Skip to content

Commit fd52118

Browse files
authored
Merge pull request #314 from hackersandslackers/feature/python10-flask3-update
Python v3.10 & Flask v3 Update
2 parents a03a96d + 147f164 commit fd52118

30 files changed

+399
-363
lines changed

.env.example

+2-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
FLASK_APP=wsgi.py
1+
ENVIRONMENT=production
22
FLASK_DEBUG=False
3-
SECRET_KEY=randomstringofcharacters
4-
LESS_BIN=/usr/local/bin/lessc
5-
ASSETS_DEBUG=False
6-
LESS_RUN_IN_DEBUG=False
7-
COMPRESSOR_DEBUG=True
3+
SECRET_KEY=randomstringofcharacters

.flake8

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[flake8]
2+
select = E9,F63,F7,F82
3+
exclude = .git,.github,__pycache__,.pytest_cache,.venv,logs,creds
4+
max-line-length = 120

.github/workflows/pythonapp.yml

+20-22
Original file line numberDiff line numberDiff line change
@@ -5,32 +5,30 @@ name: Python application
55

66
on:
77
push:
8-
branches: [ master ]
8+
branches: [master]
99
pull_request:
10-
branches: [ master ]
10+
branches: [master]
1111

1212
jobs:
1313
build:
14-
1514
runs-on: ubuntu-latest
1615

1716
steps:
18-
- uses: actions/checkout@v2
19-
- name: Set up Python 3.9
20-
uses: actions/setup-python@v2
21-
with:
22-
python-version: 3.9
23-
- name: Install dependencies
24-
run: |
25-
python -m pip install --upgrade pip
26-
pip install flake8 pytest
27-
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
28-
- name: Lint with flake8
29-
run: |
30-
# stop the build if there are Python syntax errors or undefined names
31-
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
32-
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
33-
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
34-
- name: Test with pytest
35-
run: |
36-
pytest
17+
- uses: actions/checkout@v4
18+
- uses: actions/setup-python@v5
19+
with:
20+
python-version: "3.10"
21+
cache: "pip" # caching pip dependencies
22+
23+
- name: Install dependencies
24+
run: |
25+
python -m pip install --upgrade pip
26+
pip install flake8 pytest
27+
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
28+
29+
- name: Lint with flake8
30+
run: |
31+
# stop the build if there are Python syntax errors or undefined names
32+
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
33+
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
34+
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics

LICENSE

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2020 Hackers and Slackers
3+
Copyright (c) 2023 Hackers and Slackers
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

Makefile

+1-5
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,9 @@ $(VIRTUAL_ENV):
3030
python3 -m venv $(VIRTUAL_ENV); \
3131
fi
3232

33-
.PHONY: dev
34-
dev: env
35-
$(LOCAL_PYTHON) -m main --reload
36-
3733
.PHONY: run
3834
run: env
39-
$(LOCAL_PYTHON) -m main
35+
$(LOCAL_PYTHON) -m gunicorn -w 4 wsgi:app
4036

4137
.PHONY: install
4238
install: env

README.md

+8-11
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
# Flask Blueprint Tutorial
22

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-
![Flask](https://img.shields.io/badge/Flask-v2.2.2-blue.svg?longCache=true&logo=flask&style=flat-square&logoColor=white&colorB=5e81ac&colorA=4c566a)
5-
![Flask-Assets](https://img.shields.io/badge/Flask--Assets-v2.0-blue.svg?longCache=true&logo=flask&style=flat-square&logoColor=white&colorB=5e81ac&colorA=4c566a)
3+
![Python](https://img.shields.io/badge/Python-v3.10-blue.svg?logo=python&longCache=true&logoColor=white&colorB=5e81ac&style=flat-square&colorA=4c566a)
4+
![Flask](https://img.shields.io/badge/Flask-v3.0.0-blue.svg?longCache=true&logo=flask&style=flat-square&logoColor=white&colorB=5e81ac&colorA=4c566a)
5+
![Flask-Assets](https://img.shields.io/badge/Flask--Assets-v2.1.0-blue.svg?longCache=true&logo=flask&style=flat-square&logoColor=white&colorB=5e81ac&colorA=4c566a)
6+
![Gunicorn](https://img.shields.io/badge/Gunicorn-v21.2.0-blue.svg?longCache=true&logo=gunicorn&style=flat-square&logoColor=white&colorB=a3be8c&colorA=4c566a)
67
![GitHub Last Commit](https://img.shields.io/github/last-commit/google/skia.svg?style=flat-square&colorA=4c566a&colorB=a3be8c&logo=GitHub)
78
[![GitHub Issues](https://img.shields.io/github/issues/hackersandslackers/flask-blueprint-tutorial.svg?style=flat-square&colorA=4c566a&logo=GitHub&colorB=ebcb8b)](https://github.com/hackersandslackers/flask-blueprint-tutorial/issues)
89
[![GitHub Stars](https://img.shields.io/github/stars/hackersandslackers/flask-blueprint-tutorial.svg?style=flat-square&colorA=4c566a&logo=GitHub&colorB=ebcb8b)](https://github.com/hackersandslackers/flask-blueprint-tutorial/stargazers)
@@ -13,7 +14,7 @@
1314
Structure your Flask apps in a scalable and intelligent way using Blueprints.
1415

1516
* **Tutorial**: [https://hackersandslackers.com/flask-blueprints/](https://hackersandslackers.com/flask-blueprints/)
16-
* **Demo**: [https://flaskblueprints.hackersandslackers.app/](https://flaskblueprints.hackersandslackers.app/)
17+
* **Demo**: [https://flaskblueprints.hackersandslackers.app/](https://flaskblueprints.hackersandslackers.com/)
1718

1819
## Getting Started
1920

@@ -23,13 +24,9 @@ Get set up locally in two steps:
2324

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

26-
* `FLASK_APP`: Entry point of your application; should be `wsgi.py`.
27-
* `FLASK_ENV`: The environment in which to run your application; either `development` or `production`.
27+
* `ENVIRONMENT`: The environment in which to run your application (either `development` or `production`).
28+
* `FLASK_DEBUG`: Set to `True` to enable Flask's debug mode (default to `False` in prod).
2829
* `SECRET_KEY`: Randomly generated string of characters used to encrypt your app's data.
29-
* `LESS_BIN` *(optional for static assets)*: Path to your local LESS installation via `which lessc`.
30-
* `ASSETS_DEBUG` *(optional)*: Debug asset creation and bundling in `development`.
31-
* `LESS_RUN_IN_DEBUG` *(optional)*: Debug LESS while in `development`.
32-
* `COMPRESSOR_DEBUG` *(optional)*: Debug asset compression while in `development`.
3330

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

@@ -40,7 +37,7 @@ Get up and running with `make run`:
4037
```shell
4138
git clone https://github.com/hackersandslackers/flask-blueprint-tutorial.git
4239
cd flask-blueprint-tutorial
43-
make run
40+
make deploy
4441
```
4542

4643
-----

config.py

+18-15
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,34 @@
11
"""Class-based Flask app configuration."""
2-
from os import environ, path
2+
from os import environ, path, system
33

44
from dotenv import load_dotenv
55

6-
basedir = path.abspath(path.dirname(__file__))
7-
load_dotenv(path.join(basedir, ".env"))
6+
BASE_DIR = path.abspath(path.dirname(__file__))
7+
load_dotenv(path.join(BASE_DIR, ".env"))
88

99

1010
class Config:
1111
"""Configuration from environment variables."""
1212

13+
# General Config\
14+
ENVIRONMENT = environ.get("ENVIRONMENT")
15+
16+
# Flask Config
1317
SECRET_KEY = environ.get("SECRET_KEY")
14-
FLASK_ENV = environ.get("FLASK_DEBUG")
18+
FLASK_DEBUG = environ.get("FLASK_DEBUG")
1519
FLASK_APP = "wsgi.py"
1620

17-
# Flask-Assets
18-
LESS_BIN = environ.get("LESS_BIN")
19-
ASSETS_DEBUG = True
20-
LESS_RUN_IN_DEBUG = True
21-
2221
# Static Assets
2322
STATIC_FOLDER = "static"
2423
TEMPLATES_FOLDER = "templates"
25-
COMPRESSOR_DEBUG = True
24+
COMPRESSOR_DEBUG = False
2625

27-
# Datadog
28-
DD_SERVICE = environ.get("DD_SERVICE")
29-
30-
# API
31-
BEST_BUY_API_KEY = environ.get("BEST_BUY_API_KEY")
26+
# Flask-Assets
27+
LESS_BIN = system("which lessc")
28+
ASSETS_DEBUG = False
29+
LESS_RUN_IN_DEBUG = False
30+
if ENVIRONMENT == "development" and LESS_BIN is None:
31+
raise ValueError("Application running in `development` mode cannot create assets without `lessc` installed.")
32+
33+
# Hardcoded data
34+
PRODUCT_DATA_FILEPATH = f"{BASE_DIR}/data/products.json"

data/products.json

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
[
2+
{
3+
"customerReviewAverage": 5.00,
4+
"customerReviewCount": 293,
5+
"name": "Red Robin - $25 Gift Card",
6+
"sku": 4259000,
7+
"image": "https://pisces.bbystatic.com/prescaled/500/500/image2/BestBuy_US/images/products/4259/4259000_sd.jpg",
8+
"manufacturer": "Red Robin",
9+
"longDescription": "Come in and enjoy an outrageously delicious burger with Bottomless Steak Fries. Pair it with a cold beer or signature Freckled Lemonade - it's a duo that's sure to make you smile.",
10+
"salePrice": 25.00
11+
},
12+
{
13+
"customerReviewAverage": 5.00,
14+
"customerReviewCount": 53,
15+
"name": "Bowers & Wilkins - 700 Series 3-way Floorstanding Speaker w/5\" midrange, dual 5\" bass (each) - White",
16+
"sku": 6023602,
17+
"image": "https://pisces.bbystatic.com/prescaled/500/500/image2/BestBuy_US/images/products/6023/6023602_sd.jpg",
18+
"manufacturer": "Bowers & Wilkins",
19+
"longDescription": "Generate commanding sound with this Bowers & Wilkins loudspeaker. Its Carbon Dome tweeter plays accurate, clear highs, and the two Aerofoil bass drivers deliver stiffness and rigidity while producing dynamic bass. This vented Bowers & Wilkins loudspeaker has a 5-inch midrange driver to round out its full sound, and its slim construction makes it suitable for small or large spaces.",
20+
"salePrice": 1487.99
21+
},
22+
{
23+
"customerReviewAverage": 5.00,
24+
"customerReviewCount": 53,
25+
"name": "Bowers & Wilkins - 700 Series 3-way Floorstanding Speaker w/5\" midrange, dual 5\" bass (each) - Gloss Black",
26+
"sku": 6027601,
27+
"image": "https://pisces.bbystatic.com/prescaled/500/500/image2/BestBuy_US/images/products/6027/6027601_sd.jpg",
28+
"manufacturer": "Bowers & Wilkins",
29+
"longDescription": "Enjoy accurate, realistic sound with this black Bowers & Wilkins floor speaker. Its two bass drivers deliver thumping low frequencies, and the Carbon Dome tweeter and midrange driver offer commanding, studio-quality audio. This Bowers & Wilkins floor speaker integrates into your living space and entertainment system for seamless sound production with a frequency range of 48Hz - 28kHz.",
30+
"salePrice": 1487.99
31+
},
32+
{
33+
"customerReviewAverage": 5.00,
34+
"customerReviewCount": 55,
35+
"name": "Canon - RF50mm F1.2 L USM Standard Prime Lens for EOS R-Series Cameras - Black",
36+
"sku": 6298180,
37+
"image": "https://pisces.bbystatic.com/prescaled/500/500/image2/BestBuy_US/images/products/6298/6298180_sd.jpg",
38+
"manufacturer": "Canon",
39+
"longDescription": "Capture high-quality images and sharp details with this 50mm Canon lens. The 1.31-foot minimum focusing distance and 0.19x magnification let you photograph from a range of distances, and its UD lens reduces distortion. This Canon lens has an added coating to minimize lens flare and ghosting in various types of light.",
40+
"salePrice": 2199.99
41+
},
42+
{
43+
"customerReviewAverage": 5.00,
44+
"customerReviewCount": 83,
45+
"name": "Nikkor Z 24-70mm f/2.8 S Optical Zoom Lens for Nikon Z - Black",
46+
"sku": 6334316,
47+
"image": "https://pisces.bbystatic.com/prescaled/500/500/image2/BestBuy_US/images/products/6334/6334316_sd.jpg",
48+
"manufacturer": "Nikon",
49+
"longDescription": "Capture high-quality photographs whether shooting at close, medium or long range with this Nikon NIKKOR Z 24-70mm lens. The dust-resistant and drip-resistant design helps keep this lens in good condition, and the auto-focusing feature is quick and quiet. This Nikon NIKKOR Z 24-70mm lens features a Z system that produces images with enhanced sharpness and illumination.",
50+
"salePrice": 2099.99
51+
},
52+
{
53+
"customerReviewAverage": 5.00,
54+
"customerReviewCount": 64,
55+
"name": "Apple Watch Ultra (GPS + Cellular) 49mm Titanium Case with White Ocean Band - Titanium (Verizon)",
56+
"sku": 6340050,
57+
"image": "https://pisces.bbystatic.com/prescaled/500/500/image2/BestBuy_US/images/products/6340/6340050_sd.jpg",
58+
"manufacturer": "Apple",
59+
"longDescription": "The most rugged and capable Apple Watch ever, designed for exploration, adventure, and endurance. With a 49mm aerospace-grade titanium case, extra-long battery life,¹ specialized apps that work with the advanced sensors, and a new customizable Action button. See Dimension section below for band sizing information.",
60+
"salePrice": 799.99
61+
},
62+
{
63+
"customerReviewAverage": 5.00,
64+
"customerReviewCount": 64,
65+
"name": "Apple Watch Ultra (GPS + Cellular) 49mm Titanium Case with Yellow Ocean Band - Titanium (Verizon)",
66+
"sku": 6340051,
67+
"image": "https://pisces.bbystatic.com/prescaled/500/500/image2/BestBuy_US/images/products/6340/6340051_sd.jpg",
68+
"manufacturer": "Apple",
69+
"longDescription": "The most rugged and capable Apple Watch ever, designed for exploration, adventure, and endurance. With a 49mm aerospace-grade titanium case, extra-long battery life,¹ specialized apps that work with the advanced sensors, and a new customizable Action button. See Dimension section below for band sizing information.",
70+
"salePrice": 799.99
71+
},
72+
{
73+
"customerReviewAverage": 5.00,
74+
"customerReviewCount": 64,
75+
"name": "Apple Watch Ultra (GPS + Cellular) 49mm Titanium Case with Midnight Ocean Band - Titanium (Verizon)",
76+
"sku": 6340057,
77+
"image": "https://pisces.bbystatic.com/prescaled/500/500/image2/BestBuy_US/images/products/6340/6340057_sd.jpg",
78+
"manufacturer": "Apple",
79+
"longDescription": "The most rugged and capable Apple Watch ever, designed for exploration, adventure, and endurance. With a 49mm aerospace-grade titanium case, extra-long battery life,¹ specialized apps that work with the advanced sensors, and a new customizable Action button. See Dimension section below for band sizing information.",
80+
"salePrice": 799.99
81+
},
82+
{
83+
"customerReviewAverage": 5.00,
84+
"customerReviewCount": 59,
85+
"name": "NETGEAR - 8-Port 10/100/1000 Gigabit Ethernet PoE/PoE+ Unmanaged Switch",
86+
"sku": 6356333,
87+
"image": "https://pisces.bbystatic.com/prescaled/500/500/image2/BestBuy_US/images/products/6356/6356333_sd.jpg",
88+
"manufacturer": "NETGEAR",
89+
"longDescription": "Introducing the NETGEAR GS108LP 8-port Gigabit Ethernet unmanaged switch with 60W PoE budget. The flexible PoE+ integrated technology allows you to increase or decrease the PoE budget at any time to provide to your devices the power they need with interchangeable external power supply and an intuitive power selector. The compact and fanless design makes this switch an ideal solution to connect or power any device in any business environment.",
90+
"salePrice": 98.99
91+
}
92+
]

flask_blueprint_tutorial/__init__.py

+3-5
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@
22
from flask import Flask
33
from flask_assets import Environment
44

5-
from config import Config
65

7-
8-
def init_app():
6+
def create_app():
97
"""Create Flask application."""
108
app = Flask(__name__, instance_relative_config=False)
119
app.config.from_object("config.Config")
@@ -20,8 +18,8 @@ def init_app():
2018
from .profile import profile
2119

2220
# Register Blueprints
23-
app.register_blueprint(profile.profile_bp)
24-
app.register_blueprint(home.home_bp)
21+
app.register_blueprint(profile.profile_blueprint)
22+
app.register_blueprint(home.home_blueprint)
2523
app.register_blueprint(products.product_bp)
2624

2725
# Compile static assets

flask_blueprint_tutorial/api.py

+16-17
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
1-
"""Source app with worthless data."""
2-
import requests
1+
"""Read placeholder data for demo purposes."""
2+
import json
33

4+
from flask import Flask
45

5-
def fetch_products(app):
6-
"""Grab product listings from BestBuy."""
7-
endpoint = "https://api.bestbuy.com/v1/products(customerReviewAverage>=4&customerReviewCount>100&longDescription=*)"
8-
params = {
9-
"show": "customerReviewAverage,customerReviewCount,name,sku,image,description,manufacturer,longDescription,salePrice,sku",
10-
"apiKey": app.config["BEST_BUY_API_KEY"],
11-
"format": "json",
12-
"pageSize": 6,
13-
"totalPages": 1,
14-
"sort": "customerReviewAverage.dsc",
15-
}
16-
headers = {"Accept": "application/json", "Content-Type": "application/json"}
17-
req = requests.get(endpoint, params=params, headers=headers)
18-
products = req.json()["products"]
19-
return products
6+
7+
def fetch_products(app: Flask) -> dict:
8+
"""
9+
Grab hardcoded product listings.
10+
11+
:param Flask app: Flask application object.
12+
13+
:returns: dict
14+
"""
15+
product_data_filepath = app.config["PRODUCT_DATA_FILEPATH"]
16+
with open(product_data_filepath, encoding="utf-8") as file:
17+
products_data = json.load(file)
18+
return products_data

flask_blueprint_tutorial/assets.py

+12-6
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,14 @@
33
from flask_assets import Bundle
44

55

6-
def compile_static_assets(assets):
7-
"""Create stylesheet bundles."""
6+
def compile_static_assets(assets: Bundle) -> Bundle:
7+
"""
8+
Create CSS stylesheet bundles from .less files.
9+
10+
:param Bundle assets: Static asset bundle.
11+
12+
:returns: Bundle
13+
"""
814
assets.auto_build = True
915
assets.debug = False
1016
common_style_bundle = Bundle(
@@ -14,19 +20,19 @@ def compile_static_assets(assets):
1420
extra={"rel": "stylesheet/less"},
1521
)
1622
home_style_bundle = Bundle(
17-
"home_bp/less/home.less",
23+
"home_blueprint/less/home.less",
1824
filters="less,cssmin",
1925
output="dist/css/home.css",
2026
extra={"rel": "stylesheet/less"},
2127
)
2228
profile_style_bundle = Bundle(
23-
"profile_bp/less/profile.less",
29+
"profile_blueprint/less/profile.less",
2430
filters="less,cssmin",
2531
output="dist/css/profile.css",
2632
extra={"rel": "stylesheet/less"},
2733
)
2834
product_style_bundle = Bundle(
29-
"products_bp/less/products.less",
35+
"products_blueprint/less/products.less",
3036
filters="less,cssmin",
3137
output="dist/css/products.css",
3238
extra={"rel": "stylesheet/less"},
@@ -35,7 +41,7 @@ def compile_static_assets(assets):
3541
assets.register("home_style_bundle", home_style_bundle)
3642
assets.register("profile_style_bundle", profile_style_bundle)
3743
assets.register("product_style_bundle", product_style_bundle)
38-
if app.config["FLASK_ENV"] == "development":
44+
if app.config["ENVIRONMENT"] == "development":
3945
common_style_bundle.build()
4046
home_style_bundle.build()
4147
profile_style_bundle.build()

0 commit comments

Comments
 (0)