Skip to content

Commit da1b5fe

Browse files
committed
Refactor tutorial.
1 parent f036408 commit da1b5fe

File tree

15 files changed

+131
-164
lines changed

15 files changed

+131
-164
lines changed

.env.example

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
1-
SQLALCHEMY_DATABASE_URI=mysql+pymysql://myuser:[email protected]:1234/mydatabase
2-
SQLALCHEMY_DATABASE_PEM="-----BEGIN CERTIFICATE-----\nghdfigfjvgkjdfvfjkhcvdfjhvfghjbfdvfjshdvjghvfgjvcfjdcvckdjh\n-----END CERTIFICATE-----\n"
1+
DATABASE_USERNAME="yourusername"
2+
DATABASE_PASSWORD="yourpassword"
3+
DATABASE_HOST="db.host.com"
4+
DATABASE_PORT=12345
5+
DATABASE_TABLE="table"
6+
DATABASE_CERT_FILE="ca-certificate.crt"
File renamed without changes.

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ venv/
8989
ENV/
9090
env.bak/
9191
venv.bak/
92+
creds/
93+
**/*.crt
9294

9395
# Spyder project settings
9496
.spyderproject
@@ -115,6 +117,7 @@ ca-certificate.crt
115117

116118
# Idea
117119
.idea/
120+
.vscode/
118121

119122
# logs
120123
logs/*

README.md

+16-13
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
# SQLAlchemy Tutorial
22

3-
![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)
4-
![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)
5-
![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)
3+
![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)
4+
![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)
5+
![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)
66
![GitHub Last Commit](https://img.shields.io/github/last-commit/google/skia.svg?style=flat-square&colorA=4c566a&colorB=a3be8c&logo=GitHub)
77
[![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)
88
[![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)
99
[![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)
1010

11-
![SQLAlchemy Tutorial](https://github.com/hackersandslackers/sqlalchemy-tutorial/blob/master/.github/[email protected]?raw=true)
11+
![SQLAlchemy Tutorial](https://github.com/hackersandslackers/sqlalchemy-tutorial/blob/master/.github/img/[email protected]?raw=true)
1212

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

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

20-
# Getting Started
20+
## Getting Started
2121

2222
Get set up locally in two steps:
2323

2424
### Environment Variables
2525

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

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

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

3437
### Installation
3538

36-
Get up and running with `make deploy`:
39+
Get up and running with `make run`:
3740

3841
```shell
39-
$ git clone https://github.com/hackersandslackers/sqlalchemy-tutorial.git
40-
$ cd sqlalchemy-tutorial
41-
$ make deploy
42-
```
42+
git clone https://github.com/hackersandslackers/sqlalchemy-tutorial.git
43+
cd sqlalchemy-tutorial
44+
make run
45+
```
4346

4447
-----
4548

config.py

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""Database config."""
2-
from os import environ, path
2+
from os import getenv, path
33

44
from dotenv import load_dotenv
55

@@ -8,8 +8,14 @@
88
load_dotenv(path.join(basedir, ".env"))
99

1010
# Database connection variables
11-
SQLALCHEMY_DATABASE_URI = environ.get("SQLALCHEMY_DATABASE_URI")
12-
SQLALCHEMY_DATABASE_PEM = environ.get("SQLALCHEMY_DATABASE_PEM")
11+
DATABASE_USERNAME = getenv("DATABASE_USERNAME")
12+
DATABASE_PASSWORD = getenv("DATABASE_PASSWORD")
13+
DATABASE_HOST = getenv("DATABASE_HOST")
14+
DATABASE_PORT = getenv("DATABASE_PORT")
15+
DATABASE_TABLE = getenv("DATABASE_TABLE")
16+
DATABASE_CERT_FILE = getenv("DATABASE_CERT_FILE")
17+
18+
SQLALCHEMY_DATABASE_URI = f"mysql+pymysql://{DATABASE_USERNAME}:{DATABASE_PASSWORD}@{DATABASE_HOST}:{DATABASE_PORT}/{DATABASE_TABLE}?ssl_ca={DATABASE_CERT_FILE}"
1319

1420
# Reset data after each run
1521
CLEANUP_DATA = False

database/__init__.py

+12-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,12 @@
1-
from .db import engine, session
1+
"""Create SQLAlchemy engine and session objects."""
2+
from sqlalchemy import create_engine
3+
from sqlalchemy.orm import sessionmaker
4+
5+
from config import SQLALCHEMY_DATABASE_URI
6+
7+
# Create database engine
8+
engine = create_engine(SQLALCHEMY_DATABASE_URI, echo=False)
9+
10+
# Create database session
11+
Session = sessionmaker(bind=engine)
12+
session = Session()

database/db.py

-14
This file was deleted.

logger.py

+10-25
Original file line numberDiff line numberDiff line change
@@ -8,34 +8,19 @@ def formatter(log: dict) -> str:
88
"""
99
Format log colors based on level.
1010
11-
:param log: Logged event stored as map containing contextual metadata.
12-
:type log: dict
11+
:param dict log: Logged event stored as map containing contextual metadata.
12+
1313
:returns: str
1414
"""
15+
if log["level"].name == "INFO":
16+
return "<fg #5278a3>{time:MM-DD-YYYY HH:mm:ss}</fg #5278a3> | <fg #b3cfe7>{level}</fg #b3cfe7>: <light-white>{message}</light-white>\n"
1517
if log["level"].name == "WARNING":
16-
return (
17-
"<white>{time:MM-DD-YYYY HH:mm:ss}</white> | "
18-
"<light-yellow>{level}</light-yellow>: "
19-
"<light-white>{message}</light-white> \n"
20-
)
21-
elif log["level"].name == "ERROR":
22-
return (
23-
"<white>{time:MM-DD-YYYY HH:mm:ss}</white> | "
24-
"<light-red>{level}</light-red>: "
25-
"<light-white>{message}</light-white> \n"
26-
)
27-
elif log["level"].name == "SUCCESS":
28-
return (
29-
"<white>{time:MM-DD-YYYY HH:mm:ss}</white> | "
30-
"<light-green>{level}</light-green>: "
31-
"<light-white>{message}</light-white> \n"
32-
)
33-
else:
34-
return (
35-
"<white>{time:MM-DD-YYYY HH:mm:ss}</white> | "
36-
"<fg #67c9c4>{level}</fg #67c9c4>: "
37-
"<light-white>{message}</light-white> \n"
38-
)
18+
return "<fg #5278a3>{time:MM-DD-YYYY HH:mm:ss}</fg #5278a3> | <fg #b09057>{level}</fg #b09057>: <light-white>{message}</light-white>\n"
19+
if log["level"].name == "SUCCESS":
20+
return "<fg #5278a3>{time:MM-DD-YYYY HH:mm:ss}</fg #5278a3> | <fg #6dac77>{level}</fg #6dac77>: <light-white>{message}</light-white>\n"
21+
if log["level"].name == "ERROR":
22+
return "<fg #5278a3>{time:MM-DD-YYYY HH:mm:ss}</fg #5278a3> | <fg #a35252>{level}</fg #a35252>: <light-white>{message}</light-white>\n"
23+
return "<fg #5278a3>{time:MM-DD-YYYY HH:mm:ss}</fg #5278a3> | <fg #b3cfe7>{level}</fg #b3cfe7>: <light-white>{message}</light-white>\n"
3924

4025

4126
def create_logger() -> custom_logger:

poetry.lock

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

+9-9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "sqlalchemy-tutorial"
3-
version = "0.1.0"
3+
version = "0.1.1"
44
description = "Use SQLAlchemy to connect, query, and interact with relational databases."
55
authors = ["Todd Birchard <[email protected]>"]
66
maintainers = ["Todd Birchard <[email protected]>"]
@@ -9,13 +9,7 @@ readme = "README.md"
99
homepage = "https://github.com/hackersandslackers/sqlalchemy-tutorial/"
1010
repository = "https://github.com/hackersandslackers/sqlalchemy-tutorial/"
1111
documentation = "https://github.com/hackersandslackers/sqlalchemy-tutorial/"
12-
keywords = [
13-
"SQL",
14-
"SQLAlchemy",
15-
"ORM",
16-
"Relational Databases",
17-
"RDBMS"
18-
]
12+
keywords = ["SQL", "SQLAlchemy", "ORM", "Relational Databases", "RDBMS"]
1913

2014
[tool.poetry.dependencies]
2115
python = "^3.10, <4.0"
@@ -37,5 +31,11 @@ run = "main:init_script"
3731
issues = "https://github.com/hackersandslackers/sqlalchemy-tutorial/issues"
3832

3933
[build-system]
40-
requires = ["poetry>=0.12"]
34+
requires = ["poetry>=1.6.1"]
4135
build-backend = "poetry.masonry.api"
36+
37+
[tool.black]
38+
line-length = 120
39+
40+
[tool.pylint.'MESSAGES CONTROL']
41+
disable = "C0103,C0301,W0703,W0621"

sqlalchemy_tutorial/cleanup.py

-1
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,3 @@ def cleanup_data():
2626
LOGGER.error(f"SQLAlchemyError error when resetting data: {e}")
2727
except Exception as e:
2828
LOGGER.error(f"Unexpected error when resetting data: {e}")
29-

sqlalchemy_tutorial/part1_connections/queries.py

+35-26
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from sqlalchemy import text
55
from sqlalchemy.engine.base import Engine
6+
from sqlalchemy.exc import SQLAlchemyError
67

78
from logger import LOGGER
89

@@ -11,41 +12,49 @@ def fetch_job_listings(engine: Engine) -> Optional[List[dict]]:
1112
"""
1213
Select rows from database and parse as list of dicts.
1314
14-
:param engine: Database engine to handle raw SQL queries.
15-
:type engine: engine
15+
:param Engine engine: Database engine to handle raw SQL queries.
1616
1717
:return: Optional[List[dict]]
1818
"""
19-
result = engine.execute(
20-
text(
21-
"SELECT job_id, agency, business_title, \
22-
salary_range_from, salary_range_to \
23-
FROM nyc_jobs ORDER BY RAND() LIMIT 10;"
24-
)
25-
)
26-
rows = [dict(row) for row in result.fetchall()]
27-
LOGGER.info(f"Selected {result.rowcount} rows: {rows}")
28-
return rows
19+
try:
20+
with engine.begin() as conn:
21+
result = conn.execute(
22+
text(
23+
"SELECT job_id, agency, business_title, \
24+
salary_range_from, salary_range_to \
25+
FROM nyc_jobs ORDER BY RAND() LIMIT 10;"
26+
),
27+
)
28+
results = result.fetchall()
29+
results_dict = [row._asdict() for row in results]
30+
LOGGER.info(f"Selected {result.rowcount} rows.")
31+
return results_dict
32+
except SQLAlchemyError as e:
33+
LOGGER.error(f"SQLAlchemyError while fetching records: {e}")
34+
except Exception as e:
35+
LOGGER.error(f"Unexpected error while fetching records: {e}")
2936

3037

3138
def update_job_listing(engine: Engine) -> Optional[List[dict]]:
3239
"""
3340
Update row in database with problematic characters escaped.
3441
35-
:param engine: Engine object representing a SQL database.
36-
:type engine: engine
42+
:param Engine engine: Database engine to handle raw SQL queries.
3743
3844
:return: Optional[List[dict]]
3945
"""
40-
result = engine.execute(
41-
text(
42-
"UPDATE nyc_jobs SET business_title = 'Senior QA Scapegoat 🏆', \
43-
job_category = 'Information? <>!#%%Technology!%%#^&%* & Telecom' \
44-
WHERE job_id = 229837;"
45-
)
46-
)
47-
LOGGER.info(
48-
f"Selected {result.rowcount} row: \
49-
{result}"
50-
)
51-
return result.rowcount
46+
try:
47+
with engine.begin() as conn:
48+
result = conn.execute(
49+
text(
50+
"UPDATE nyc_jobs SET business_title = 'Senior QA Scapegoat 🏆', \
51+
job_category = 'Information? <>!#%%Technology!%%#^&%* & Telecom' \
52+
WHERE job_id = 229837;"
53+
)
54+
)
55+
LOGGER.info(f"Updated {result.rowcount} row: {result}")
56+
return result
57+
except SQLAlchemyError as e:
58+
LOGGER.error(f"SQLAlchemyError while updating records: {e}")
59+
except Exception as e:
60+
LOGGER.error(f"Unexpected error while updating records: {e}")

0 commit comments

Comments
 (0)