Skip to content

Commit dd873a0

Browse files
committed
Organize repo by series chapter.
1 parent 776b392 commit dd873a0

File tree

20 files changed

+198
-72
lines changed

20 files changed

+198
-72
lines changed

Diff for: .env.example

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
SQLALCHEMY_DATABASE_URI=mysql+pymysql://myuser:[email protected]:1234/mydatabase
2+
SQLALCHEMY_DATABASE_PEM="-----BEGIN CERTIFICATE-----\nghdfigfjvgkjdfvfjkhcvdfjhvfghjbfdvfjshdvjghvfgjvcfjdcvckdjh\n-----END CERTIFICATE-----\n"

Diff for: Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ all help:
3131

3232
.PHONY: run
3333
run: env
34-
flask run
34+
python main.py
3535

3636

3737
.PHONY: deploy

Diff for: README.md

+7-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@
1010

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

13-
**Tutorial**: https://hackersandslackers.com/python-database-management-sqlalchemy/
13+
This repository contains the source code for a four-part tutorial series on SQLAlchemy:
14+
15+
1. [Databases in Python Made Easy with SQLAlchemy](https://hackersandslackers.com/python-database-management-sqlalchemy)
16+
2. [Implement an ORM with SQLAlchemy](https://hackersandslackers.com/implement-sqlalchemy-orm)
17+
3. [Relationships in SQLAlchemy Data Models](https://hackersandslackers.com/sqlalchemy-data-models)
18+
4. [Constructing Database Queries with SQLAlchemy](https://hackersandslackers.com/database-queries-sqlalchemy-orm)
1419

1520
# Getting Started
1621

@@ -22,6 +27,7 @@ Replace the values in **.env.example** with your values and rename this file to
2227

2328

2429
* `SQLALCHEMY_DATABASE_URI`: Connection URI of a SQL database.
30+
* `SQLALCHEMY_DATABASE_PEM` _(Optional)_: PEM key for databases requiring an SSL connection.
2531

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

Diff for: config.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
"""Database config."""
2-
from datetime import datetime
32
from os import environ, path
43

54
from dotenv import load_dotenv
@@ -8,7 +7,6 @@
87
basedir = path.abspath(path.dirname(__file__))
98
load_dotenv(path.join(basedir, ".env"))
109

11-
10+
# Database connection variables
1211
SQLALCHEMY_DATABASE_URI = environ.get("SQLALCHEMY_DATABASE_URI")
1312
SQLALCHEMY_DATABASE_PEM = environ.get("SQLALCHEMY_DATABASE_PEM")
14-
TIME_NOW = datetime.now()
File renamed without changes.

Diff for: sqlalchemy_tutorial/database/connect.py renamed to database/connect.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Create database connection."""
1+
"""Create SQLAlchemy engine and session objects."""
22
from sqlalchemy import create_engine
33
from sqlalchemy.orm import sessionmaker
44

Diff for: main.py

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
"""Script entry point."""
12
from sqlalchemy_tutorial import init_script
23

34
if __name__ == "__main__":

Diff for: poetry.lock

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

Diff for: pyproject.toml

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ python = "^3.8"
2222
sqlalchemy = "*"
2323
pymysql = "*"
2424
python-dotenv = "*"
25+
loguru = "^0.5.3"
2526

2627
[tool.poetry.dev-dependencies]
2728
pytest = "*"

Diff for: requirements.txt

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
colorama==0.4.4; python_version >= "3.5" and python_full_version < "3.0.0" and sys_platform == "win32" or sys_platform == "win32" and python_version >= "3.5" and python_full_version >= "3.5.0"
2+
loguru==0.5.3; python_version >= "3.5"
13
pymysql==1.0.2; python_version >= "3.6"
24
python-dotenv==0.15.0
35
sqlalchemy==1.3.22; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0")
6+
win32-setctime==1.0.3; sys_platform == "win32" and python_version >= "3.5"

Diff for: sqlalchemy_tutorial/__init__.py

+16-17
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,20 @@
1-
"""Script entry point."""
2-
import pprint
1+
"""Run source code for each tutorial found on HackersAndSlackers."""
2+
from database import db, session
33

4-
from sqlalchemy_tutorial.database.connect import db
5-
from sqlalchemy_tutorial.database.models import create_tables
6-
from sqlalchemy_tutorial.orm import orm_create_user
7-
from sqlalchemy_tutorial.queries import fetch_job_listings, update_job_listing
8-
9-
# Print formatter
10-
pp = pprint.PrettyPrinter(indent=4, width=300)
4+
from .logger import LOGGER
5+
from .part1_connections import fetch_job_listings, update_job_listing
6+
from .part2_orm_models import orm_create_user, orm_delete_user
117

128

139
def init_script():
14-
"""Demonstrate SELECT and UPDATE queries with SQLAlchemy."""
15-
create_tables()
16-
rows_selected = fetch_job_listings(db)
17-
pp.pprint(rows_selected)
18-
rows_updated = update_job_listing(db)
19-
print(rows_updated)
20-
user = orm_create_user()
21-
print(user)
10+
"""Run all scripts."""
11+
12+
# Part 1: Executing SELECT and UPDATE queries with an SQLAlchemy engine
13+
LOGGER.info("Part 1: Executing queries against an SQLAlchemy engine.")
14+
fetch_job_listings(db)
15+
update_job_listing(db)
16+
17+
# Part 2: Implementing an ORM
18+
LOGGER.info("Part 2: Create and delete records from a data model.")
19+
orm_create_user(session)
20+
orm_delete_user(session)

Diff for: sqlalchemy_tutorial/logger.py

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
"""Custom logger."""
2+
from sys import stdout
3+
4+
from loguru import logger as custom_logger
5+
6+
7+
def create_logger() -> custom_logger:
8+
"""Create custom logger."""
9+
custom_logger.remove()
10+
custom_logger.add(
11+
stdout,
12+
colorize=True,
13+
catch=True,
14+
format="<light-cyan>{time:MM-DD-YYYY HH:mm:ss}</light-cyan> | "
15+
+ "<light-green>{level}</light-green>: "
16+
+ "<light-white>{message}</light-white>",
17+
)
18+
return custom_logger
19+
20+
21+
LOGGER = create_logger()

Diff for: sqlalchemy_tutorial/orm.py

-19
This file was deleted.

Diff for: sqlalchemy_tutorial/part1_connections/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .engines import fetch_job_listings, update_job_listing
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,51 @@
1-
"""Executing raw SQL queries against an SQLAlchemy engine."""
1+
"""Execute raw SQL queries against an SQLAlchemy engine."""
22
from typing import List, Optional
33

44
from sqlalchemy import engine, text
55

6+
from sqlalchemy_tutorial.logger import LOGGER
7+
68

79
def fetch_job_listings(db: engine) -> Optional[List[dict]]:
8-
"""Select rows from database and parse as list of dicts."""
10+
"""
11+
Select rows from database and parse as list of dicts.
12+
13+
:param db: Engine object representing a SQL database.
14+
:type db: engine
15+
16+
:returns: Optional[List[dict]]
17+
"""
918
results = db.execute(
1019
"SELECT job_id, agency, business_title, \
1120
salary_range_from, salary_range_to \
1221
FROM nyc_jobs ORDER BY RAND();"
1322
)
14-
print(f"Selected {results.rowcount} rows.")
1523
rows = [dict(row) for row in results.fetchall()]
24+
LOGGER.info(
25+
f"Selected {results.rowcount} rows: \
26+
{rows}"
27+
)
1628
return rows
1729

1830

1931
def update_job_listing(db: engine) -> Optional[List[dict]]:
20-
"""Update row in database with problematic characters escaped."""
32+
"""
33+
Update row in database with problematic characters escaped.
34+
35+
:param db: Engine object representing a SQL database.
36+
:type db: engine
37+
38+
:returns: Optional[List[dict]]
39+
"""
2140
result = db.execute(
2241
text(
2342
"UPDATE nyc_jobs SET business_title = 'Senior QA Scapegoat 🏆', \
2443
job_category = 'Information? <>!#%%Technology!%%#^&%* & Telecom' \
2544
WHERE job_id = 229837;"
2645
)
2746
)
47+
LOGGER.info(
48+
f"Selected {result.rowcount} row: \
49+
{result}"
50+
)
2851
return result.rowcount

Diff for: sqlalchemy_tutorial/part2_orm_models/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .orm import orm_create_user, orm_delete_user

Diff for: sqlalchemy_tutorial/part2_orm_models/models.py

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""Declare models and relationships."""
2+
from sqlalchemy import Column, DateTime, Integer, String, Text
3+
from sqlalchemy.ext.declarative import declarative_base
4+
from sqlalchemy.sql import func
5+
6+
from database import db
7+
8+
Base = declarative_base()
9+
10+
11+
class User(Base):
12+
"""User account."""
13+
14+
__tablename__ = "user"
15+
16+
id = Column(Integer, primary_key=True, autoincrement="auto")
17+
username = Column(String(255), unique=True, nullable=False)
18+
password = Column(Text, nullable=False)
19+
email = Column(String(255), unique=True, nullable=False)
20+
first_name = Column(String(255))
21+
last_name = Column(String(255))
22+
bio = Column(Text)
23+
avatar_url = Column(Text)
24+
last_seen = Column(DateTime)
25+
created_at = Column(DateTime, server_default=func.now())
26+
updated_at = Column(DateTime, onupdate=func.now())
27+
28+
def __repr__(self):
29+
return "<User %r>" % self.username
30+
31+
32+
Base.metadata.create_all(db)

Diff for: sqlalchemy_tutorial/part2_orm_models/orm.py

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
"""Create, delete and update records with SQLAlchemy's ORM."""
2+
from sqlalchemy.orm import Session
3+
4+
from sqlalchemy_tutorial.logger import LOGGER
5+
from sqlalchemy_tutorial.part2_orm_models.models import User
6+
7+
# Define instance of our `User` model
8+
user = User(
9+
username="admin",
10+
password="Please don't set passwords like this",
11+
12+
first_name="Todd",
13+
last_name="Birchard",
14+
bio="I write tutorials on the internet.",
15+
avatar_url="https://storage.googleapis.com/hackersandslackers-cdn/authors/[email protected]",
16+
)
17+
18+
19+
def orm_create_user(session: Session):
20+
"""
21+
Create a new instance of our `User` model.
22+
23+
:param session: SQLAlchemy database session.
24+
:type session: Session
25+
26+
:returns: None
27+
"""
28+
session.add(user) # Add the user
29+
session.commit() # Commit the change
30+
31+
LOGGER.info(f"Create new user: {user}")
32+
33+
34+
def orm_delete_user(session: Session):
35+
"""
36+
Delete a user if it exists.
37+
38+
:param session: SQLAlchemy database session.
39+
:type session: Session
40+
41+
:returns: None
42+
"""
43+
session.delete(user) # Delete the user
44+
session.commit() # Commit the change
45+
46+
LOGGER.info(f"Deleted existing user: {user}")

Diff for: sqlalchemy_tutorial/part3_relationships/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)