├── .env.example
├── .flake8
├── .github
├── CODEOWNERS
├── FUNDING.yml
├── dependabot.yml
├── img
│ └── flaskblueprints@2x.jpg
└── workflows
│ └── pythonapp.yml
├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── config.py
├── data
└── products.json
├── flask_blueprint_tutorial
├── __init__.py
├── api.py
├── assets.py
├── home
│ ├── home.py
│ ├── static
│ │ └── less
│ │ │ ├── home.less
│ │ │ └── variables.less
│ └── templates
│ │ └── index.jinja2
├── product
│ ├── product.py
│ ├── static
│ │ └── less
│ │ │ ├── product.less
│ │ │ └── vars.less
│ └── templates
│ │ └── product.jinja2
├── profile
│ ├── profile.py
│ ├── static
│ │ ├── img
│ │ │ └── avatar.png
│ │ └── less
│ │ │ └── profile.less
│ └── templates
│ │ └── profile.jinja2
├── static
│ ├── dist
│ │ ├── css
│ │ │ ├── home.css
│ │ │ ├── product.css
│ │ │ ├── profile.css
│ │ │ └── style.css
│ │ └── img
│ │ │ ├── avatar.png
│ │ │ ├── favicon@2x.png
│ │ │ └── logo@2x.png
│ └── src
│ │ └── less
│ │ ├── nav.less
│ │ ├── style.less
│ │ └── variables.less
└── templates
│ ├── analytics.jinja2
│ ├── blueprintinfo.jinja2
│ ├── layout.jinja2
│ └── navigation.jinja2
├── mypy.ini
├── poetry.lock
├── pyproject.toml
├── renovate.json
├── requirements.txt
└── wsgi.py
/.env.example:
--------------------------------------------------------------------------------
1 | ENVIRONMENT=production
2 | FLASK_DEBUG=False
3 | SECRET_KEY=randomstringofcharacters
--------------------------------------------------------------------------------
/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | select = E9,F63,F7,F82
3 | exclude = .git,.github,__pycache__,.pytest_cache,.venv,logs,creds
4 | max-line-length = 120
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | @hackersandslackers/hackers-contributors
2 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | custom: ['https://www.buymeacoffee.com/hackersslackers']
4 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: pip
4 | directory: "/"
5 | schedule:
6 | interval: daily
7 | time: "10:00"
8 | open-pull-requests-limit: 10
9 | ignore:
10 | - dependency-name: black
11 | versions:
12 | - 23.12.1
13 | - dependency-name: faker
14 | versions:
15 | - 5.8.0
16 | - 6.0.0
17 | - 6.1.1
18 | - 6.3.0
19 | - 6.4.1
20 | - 6.5.0
21 | - 6.6.0
22 | - 6.6.1
23 | - 6.6.2
24 | - 6.6.3
25 | - 7.0.1
26 | - 8.0.0
27 | - 8.1.0
28 | - dependency-name: ddtrace
29 | versions:
30 | - 0.46.0
31 | - 0.47.0
32 | - 0.48.0
33 | - dependency-name: python-dotenv
34 | versions:
35 | - 0.16.0
36 | - dependency-name: urllib3
37 | versions:
38 | - 1.26.3
39 | - dependency-name: pytest
40 | versions:
41 | - 7.4.3
42 | - dependency-name: isort
43 | versions:
44 | - 5.13.2
45 |
--------------------------------------------------------------------------------
/.github/img/flaskblueprints@2x.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackersandslackers/flask-blueprint-tutorial/44120992b8507b140eedcf5fc78870a8a09130be/.github/img/flaskblueprints@2x.jpg
--------------------------------------------------------------------------------
/.github/workflows/pythonapp.yml:
--------------------------------------------------------------------------------
1 | # This workflow will install Python dependencies, run tests and lint with a single version of Python
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
3 |
4 | name: Python application
5 |
6 | on:
7 | push:
8 | branches: [master]
9 | pull_request:
10 | branches: [master]
11 |
12 | jobs:
13 | build:
14 | runs-on: ubuntu-latest
15 |
16 | steps:
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
35 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | downloads/
14 | eggs/
15 | .eggs/
16 | lib/
17 | lib64/
18 | parts/
19 | sdist/
20 | var/
21 | wheels/
22 | *.egg-info/
23 | .installed.cfg
24 | *.egg
25 | MANIFEST
26 |
27 | # PyInstaller
28 | # Usually these files are written by a python script from a templates
29 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
30 | *.manifest
31 | *.spec
32 |
33 | # Installer logs
34 | pip-log.txt
35 | pip-delete-this-directory.txt
36 |
37 | # Unit test / coverage reports
38 | htmlcov/
39 | .tox/
40 | .coverage
41 | .coverage.*
42 | .cache
43 | nosetests.xml
44 | coverage.xml
45 | *.cover
46 | .hypothesis/
47 | .pytest_cache/
48 |
49 | # Translations
50 | *.mo
51 | *.pot
52 |
53 | # Django stuff:
54 | *.log
55 | local_settings.py
56 | db.sqlite3
57 |
58 | # Flask stuff:
59 | instance/
60 | .webassets-cache
61 |
62 | # Scrapy stuff:
63 | .scrapy
64 |
65 | # Sphinx documentation
66 | docs/_build/
67 |
68 | # PyBuilder
69 | target/
70 |
71 | # Jupyter Notebook
72 | .ipynb_checkpoints
73 |
74 | # pyenv
75 | .python-version
76 |
77 | # celery beat schedule file
78 | celerybeat-schedule
79 |
80 | # SageMath parsed files
81 | *.sage.py
82 |
83 | # Environments
84 | .env
85 | .venv
86 | env/
87 | venv/
88 | ENV/
89 | env.bak/
90 | venv.bak/
91 |
92 | # Spyder project settings
93 | .spyderproject
94 | .spyproject
95 |
96 | # Rope project settings
97 | .ropeproject
98 |
99 | # mkdocs documentation
100 | /site
101 |
102 | # MyPy
103 | .mypy_cache/
104 |
105 | # IDEs
106 | .idea
107 | .vscode
108 |
109 | # Etc
110 | .DS_Store
111 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Hackers and Slackers
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | PROJECT_NAME := $(shell basename $CURDIR)
2 | VIRTUAL_ENV := $(CURDIR)/.venv
3 | LOCAL_PYTHON := $(VIRTUAL_ENV)/bin/python3
4 |
5 | define HELP
6 | Manage $(PROJECT_NAME). Usage:
7 |
8 | make run - Run $(PROJECT_NAME) locally.
9 | make install - Create local virtualenv & install dependencies.
10 | make deploy - Set up project & run locally.
11 | make update - Update dependencies via Poetry and output resulting `requirements.txt`.
12 | make format - Run Python code formatter & sort dependencies.
13 | make lint - Check code formatting with flake8.
14 | make clean - Remove extraneous compiled files, caches, logs, etc.
15 |
16 | endef
17 | export HELP
18 |
19 |
20 | .PHONY: run install deploy update format lint clean help
21 |
22 | all help:
23 | @echo "$$HELP"
24 |
25 | env: $(VIRTUAL_ENV)
26 |
27 | $(VIRTUAL_ENV):
28 | if [ ! -d $(VIRTUAL_ENV) ]; then \
29 | echo "Creating Python virtual env in \`${VIRTUAL_ENV}\`"; \
30 | python3 -m venv $(VIRTUAL_ENV); \
31 | fi
32 |
33 | .PHONY: run
34 | run: env
35 | $(LOCAL_PYTHON) -m gunicorn -w 4 wsgi:app
36 |
37 | .PHONY: install
38 | install: env
39 | $(LOCAL_PYTHON) -m pip install --upgrade pip setuptools wheel && \
40 | $(LOCAL_PYTHON) -m pip install -r requirements.txt && \
41 | echo Installed dependencies in \`${VIRTUAL_ENV}\`;
42 |
43 | .PHONY: deploy
44 | deploy:
45 | make install && \
46 | make run
47 |
48 | .PHONY: test
49 | test: env
50 | $(LOCAL_PYTHON) -m \
51 | coverage run -m pytest -vv \
52 | --disable-pytest-warnings && \
53 | coverage html --title='Coverage Report' -d .reports && \
54 | open .reports/index.html
55 |
56 | .PHONY: update
57 | update: env
58 | $(LOCAL_PYTHON) -m pip install --upgrade pip setuptools wheel && \
59 | poetry update && \
60 | poetry export -f requirements.txt --output requirements.txt --without-hashes && \
61 | echo Installed dependencies in \`${VIRTUAL_ENV}\`;
62 |
63 | .PHONY: format
64 | format: env
65 | $(LOCAL_PYTHON) -m isort --multi-line=3 . && \
66 | $(LOCAL_PYTHON) -m black .
67 |
68 | .PHONY: lint
69 | lint: env
70 | $(LOCAL_PYTHON) -m flake8 . --count \
71 | --select=E9,F63,F7,F82 \
72 | --exclude .git,.github,__pycache__,.pytest_cache,.venv,logs,creds,.venv,docs,logs,.reports \
73 | --show-source \
74 | --statistics
75 |
76 | .PHONY: clean
77 | clean:
78 | find . -name 'poetry.lock' -delete && \
79 | find . -name '.coverage' -delete && \
80 | find . -name '.Pipfile.lock' -delete && \
81 | find . -wholename '**/*.pyc' -delete && \
82 | find . -type d -wholename '__pycache__' -exec rm -rf {} + && \
83 | find . -type d -wholename './.venv' -exec rm -rf {} + && \
84 | find . -type d -wholename '.pytest_cache' -exec rm -rf {} + && \
85 | find . -type d -wholename '**/.pytest_cache' -exec rm -rf {} + && \
86 | find . -type d -wholename './logs/*.log' -exec rm -rf {} + && \
87 | find . -type d -wholename './.reports/*' -exec rm -rf {} +
88 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Flask Blueprint Tutorial
2 |
3 | 
4 | 
5 | 
6 | 
7 | 
8 | [](https://github.com/hackersandslackers/flask-blueprint-tutorial/issues)
9 | [](https://github.com/hackersandslackers/flask-blueprint-tutorial/stargazers)
10 | [](https://github.com/hackersandslackers/flask-blueprint-tutorial/network)
11 |
12 | 
13 |
14 | Structure your Flask apps in a scalable and intelligent way using Blueprints.
15 |
16 | * **Tutorial**: [https://hackersandslackers.com/flask-blueprints/](https://hackersandslackers.com/flask-blueprints/)
17 | * **Demo**: [https://flaskblueprints.hackersandslackers.app/](https://flaskblueprints.hackersandslackers.com/)
18 |
19 | ## Getting Started
20 |
21 | Get set up locally in two steps:
22 |
23 | ### Environment Variables
24 |
25 | Replace the values in **.env.example** with your values and rename this file to **.env**:
26 |
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).
29 | * `SECRET_KEY`: Randomly generated string of characters used to encrypt your app's data.
30 |
31 | *Remember never to commit secrets saved in .env files to Github.*
32 |
33 | ### Installation
34 |
35 | Get up and running with `make run`:
36 |
37 | ```shell
38 | git clone https://github.com/hackersandslackers/flask-blueprint-tutorial.git
39 | cd flask-blueprint-tutorial
40 | make deploy
41 | ```
42 |
43 | -----
44 |
45 | **Hackers and Slackers** tutorials are free of charge. If you found this tutorial helpful, a [small donation](https://www.buymeacoffee.com/hackersslackers) would be greatly appreciated to keep us in business. All proceeds go towards coffee, and all coffee goes towards more content.
46 |
--------------------------------------------------------------------------------
/config.py:
--------------------------------------------------------------------------------
1 | """Class-based Flask app configuration."""
2 | import subprocess
3 | from os import environ, path
4 |
5 | from dotenv import load_dotenv
6 |
7 | BASE_DIR = path.abspath(path.dirname(__file__))
8 | load_dotenv(path.join(BASE_DIR, ".env"))
9 |
10 |
11 | class Config:
12 | """Configuration from environment variables."""
13 |
14 | # General Config\
15 | ENVIRONMENT = environ.get("ENVIRONMENT")
16 |
17 | # Flask Config
18 | SECRET_KEY = environ.get("SECRET_KEY")
19 | FLASK_DEBUG = environ.get("FLASK_DEBUG")
20 | FLASK_APP = "wsgi.py"
21 |
22 | # Static Assets
23 | STATIC_FOLDER = "static"
24 | TEMPLATES_FOLDER = "templates"
25 | COMPRESSOR_DEBUG = False
26 |
27 | # Flask-Assets
28 | if ENVIRONMENT == "development":
29 | # Check if `lessc` is installed
30 | LESS_BIN = subprocess.call("which lessc", shell=True)
31 | if LESS_BIN is None:
32 | raise ValueError("Flask requires `lessc` to be installed to compile styles.")
33 | else:
34 | # Check if `nodejs` is installed
35 | NODE_JS = subprocess.call("which node", shell=True)
36 | if NODE_JS is None:
37 | raise ValueError(
38 | "Application running in `development` mode cannot create assets without `node` installed."
39 | )
40 |
41 | # Hardcoded data
42 | PRODUCT_DATA_FILEPATH = f"{BASE_DIR}/data/products.json"
43 |
--------------------------------------------------------------------------------
/data/products.json:
--------------------------------------------------------------------------------
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:
--------------------------------------------------------------------------------
1 | """Initialize Flask app."""
2 | from flask import Flask
3 | from flask_assets import Environment
4 |
5 |
6 | def create_app():
7 | """Create Flask application."""
8 | app = Flask(__name__, instance_relative_config=False)
9 | app.config.from_object("config.Config")
10 | assets = Environment()
11 | assets.init_app(app)
12 |
13 | with app.app_context():
14 | # Import parts of our application
15 | from .assets import compile_static_assets
16 | from .home import home
17 | from .product import product
18 | from .profile import profile
19 |
20 | # Register Blueprints
21 | app.register_blueprint(profile.profile_blueprint)
22 | app.register_blueprint(home.home_blueprint)
23 | app.register_blueprint(product.product_blueprint)
24 |
25 | # Compile static assets
26 | compile_static_assets(assets)
27 |
28 | return app
29 |
--------------------------------------------------------------------------------
/flask_blueprint_tutorial/api.py:
--------------------------------------------------------------------------------
1 | """Read placeholder data for demo purposes."""
2 | import json
3 |
4 | from flask import Flask
5 |
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
19 |
--------------------------------------------------------------------------------
/flask_blueprint_tutorial/assets.py:
--------------------------------------------------------------------------------
1 | """Compile static assets."""
2 | from flask import current_app as app
3 | from flask_assets import Bundle
4 |
5 |
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 | """
14 | assets.auto_build = True
15 | assets.debug = False
16 | common_style_bundle = Bundle(
17 | "src/less/*.less",
18 | filters="less,cssmin",
19 | output="dist/css/style.css",
20 | extra={"rel": "stylesheet/less"},
21 | )
22 | home_style_bundle = Bundle(
23 | "home_blueprint/less/home.less",
24 | filters="less,cssmin",
25 | output="dist/css/home.css",
26 | extra={"rel": "stylesheet/less"},
27 | )
28 | profile_style_bundle = Bundle(
29 | "profile_blueprint/less/profile.less",
30 | filters="less,cssmin",
31 | output="dist/css/profile.css",
32 | extra={"rel": "stylesheet/less"},
33 | )
34 | product_style_bundle = Bundle(
35 | "product_blueprint/less/product.less",
36 | filters="less,cssmin",
37 | output="dist/css/product.css",
38 | extra={"rel": "stylesheet/less"},
39 | )
40 | assets.register("common_style_bundle", common_style_bundle)
41 | assets.register("home_style_bundle", home_style_bundle)
42 | assets.register("profile_style_bundle", profile_style_bundle)
43 | assets.register("product_style_bundle", product_style_bundle)
44 | if app.config["ENVIRONMENT"] == "development":
45 | common_style_bundle.build()
46 | home_style_bundle.build()
47 | profile_style_bundle.build()
48 | product_style_bundle.build()
49 | return assets
50 |
--------------------------------------------------------------------------------
/flask_blueprint_tutorial/home/home.py:
--------------------------------------------------------------------------------
1 | """General page routes."""
2 | from flask import Blueprint
3 | from flask import current_app as app
4 | from flask import render_template
5 |
6 | from flask_blueprint_tutorial.api import fetch_products
7 |
8 | # Blueprint Configuration
9 | home_blueprint = Blueprint("home_blueprint", __name__, template_folder="templates", static_folder="static")
10 |
11 |
12 | @home_blueprint.route("/", methods=["GET"])
13 | def home() -> str:
14 | """
15 | Serve `Home` page template.
16 |
17 | :returns: str
18 | """
19 | products = fetch_products(app)
20 | return render_template(
21 | "index.jinja2",
22 | title="Flask Blueprint Demo",
23 | subtitle="Demonstration of Flask blueprints in action.",
24 | template="home-template",
25 | products=products,
26 | )
27 |
28 |
29 | @home_blueprint.route("/about", methods=["GET"])
30 | def about() -> str:
31 | """
32 | Serve `About` page template.
33 |
34 | :returns: str
35 | """
36 | return render_template(
37 | "index.jinja2",
38 | title="About",
39 | subtitle="This is an example about page.",
40 | template="home-template page",
41 | )
42 |
43 |
44 | @home_blueprint.route("/contact", methods=["GET"])
45 | def contact() -> str:
46 | """
47 | Serve `Contact` page template.
48 |
49 | :returns: str
50 | """
51 | return render_template(
52 | "index.jinja2",
53 | title="Contact",
54 | subtitle="This is an example contact page.",
55 | template="home-template page",
56 | )
57 |
--------------------------------------------------------------------------------
/flask_blueprint_tutorial/home/static/less/home.less:
--------------------------------------------------------------------------------
1 | @import 'variables.less';
2 |
3 | .home-template {
4 |
5 | &.page {
6 | .products {
7 | display: none !important;
8 | }
9 | }
10 |
11 | .container {
12 | .resource-links {
13 | margin-top: 10px;
14 |
15 | .resource-link {
16 | margin: 4px 0;
17 | color: #8a91a7;
18 | line-height: 1;
19 | }
20 | }
21 |
22 | .products {
23 | display: flex;
24 | justify-content: space-between;
25 | flex-wrap: wrap;
26 | margin-top: 30px;
27 |
28 | .product-preview {
29 | padding: 2%;
30 | box-shadow: 0 0 5px rgba(65, 67, 144, 0.15);
31 | max-width: 28%;
32 | width: 30%;
33 | background: white;
34 | margin-bottom: 20px;
35 | display: block;
36 | text-align: center;
37 | overflow: hidden;
38 | transition: @transition;
39 |
40 | &:hover {
41 | background: #5dbad7;
42 | color: white !important;
43 | opacity: 1;
44 |
45 | * {
46 | color: white !important;
47 | }
48 | }
49 |
50 | .product-image {
51 | height: 95px;
52 | margin: 0 auto 15px;
53 | display: block;
54 | @media (max-width: 600px) {
55 | width: 90%;
56 | height: unset;
57 | }
58 | }
59 |
60 | .name {
61 | -webkit-line-clamp: 2;
62 | -webkit-box-orient: vertical;
63 | display: -webkit-box;
64 | width: -webkit-fill-available;
65 | width: -moz-available;
66 | width: stretch;
67 | overflow: hidden;
68 | text-overflow: ellipsis;
69 | font-weight: 500;
70 | font-size: 1em;
71 | line-height: 1.3;
72 | height: 60px;
73 | }
74 |
75 | .price {
76 | color: #5f6988;
77 | margin-top: 5px;
78 | font-size: 1.1em;
79 | font-weight: 600;
80 | }
81 | }
82 | }
83 | }
84 |
85 | h2 {
86 | margin-bottom: 0 !important;
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/flask_blueprint_tutorial/home/static/less/variables.less:
--------------------------------------------------------------------------------
1 | // Colors
2 | @theme-color: #5eb9d7;
3 | @background-color: #e1eaf5;
4 | @box-shadow: 0 0 5px rgba(65, 67, 144, 0.15);
5 | @header-color: #5f6988;
6 |
7 | // Fonts
8 | @body-font: 'Poppins', sans-serif;
9 |
10 | // Animation
11 | @transition: all .2s ease-out;
12 |
--------------------------------------------------------------------------------
/flask_blueprint_tutorial/home/templates/index.jinja2:
--------------------------------------------------------------------------------
1 | {% extends "layout.jinja2" %}
2 |
3 | {% block pagestyles %}
4 | {% assets "home_style_bundle" %}
5 |
6 | {% endassets %}
7 | {% endblock %}
8 |
9 | {% block content %}
10 | {% include "navigation.jinja2" %}
11 |
12 |
13 |
{{ title }}
14 |
{{ subtitle }}
15 |
19 |
20 |
29 |
30 |
31 | {% include "blueprintinfo.jinja2" %}
32 |
33 | {% endblock %}
34 |
--------------------------------------------------------------------------------
/flask_blueprint_tutorial/product/product.py:
--------------------------------------------------------------------------------
1 | """Product pages."""
2 | from flask import Blueprint
3 | from flask import current_app as app
4 | from flask import render_template
5 |
6 | from flask_blueprint_tutorial.api import fetch_products
7 |
8 | # Blueprint Configuration
9 | product_blueprint = Blueprint("product_blueprint", __name__, template_folder="templates", static_folder="static")
10 |
11 |
12 | @product_blueprint.route("/products//", methods=["GET"])
13 | def product_page(product_id: int) -> str:
14 | """
15 | Product detail page for a given product ID.
16 |
17 | :params int product_id: Unique product ID.
18 |
19 | :returns: str
20 | """
21 | product = fetch_products(app)[product_id]
22 | return render_template(
23 | "product.jinja2",
24 | title=product["name"],
25 | product=product,
26 | template="product-template",
27 | )
28 |
--------------------------------------------------------------------------------
/flask_blueprint_tutorial/product/static/less/product.less:
--------------------------------------------------------------------------------
1 | @import 'vars.less';
2 |
3 | .product-template {
4 | .product-name {
5 | font-size: 1.9em;
6 | margin-bottom: 30px !important;
7 | line-height: 1.25em;
8 | }
9 |
10 | .product-image {
11 | height: 250px;
12 | max-width: 30vw;
13 | margin-right: 40px;
14 | border-radius: 3px;
15 | }
16 |
17 | .product-details {
18 | display: grid;
19 | grid-template-columns: 1fr 2fr;
20 |
21 | .product-info {
22 | display: flex;
23 | justify-content: space-between;
24 | flex-direction: column;
25 | }
26 | }
27 |
28 | .meta {
29 | font-size: 1.1em;
30 |
31 | .meta-name {
32 | font-weight: 600;
33 | width: 114px;
34 | display: inline-block;
35 | }
36 | }
37 |
38 | p {
39 | font-size: 0.85em;
40 | line-height: 1.5;
41 | }
42 |
43 | button {
44 | background: @theme-color;
45 | color: white;
46 | border-radius: 2px;
47 | margin-top: 10px;
48 | font-weight: 400;
49 | border: 1px solid @theme-color;
50 | transition: @transition;
51 | padding: 10px;
52 | font-size: 1em;
53 | font-weight: bold;
54 | max-width: 283px;
55 |
56 | &:hover {
57 | cursor: pointer;
58 | background: white;
59 | color: @theme-color;
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/flask_blueprint_tutorial/product/static/less/vars.less:
--------------------------------------------------------------------------------
1 | // Colors
2 | @theme-color: #5eb9d7;
3 | @background-color: #e1eaf5;
4 | @box-shadow: 0 0 5px rgba(65, 67, 144, 0.15);
5 | @header-color: #5f6988;
6 |
7 | // Fonts
8 | @body-font: 'Poppins', sans-serif;
9 |
10 | // Animation
11 | @transition: all .3s ease-out;
12 |
--------------------------------------------------------------------------------
/flask_blueprint_tutorial/product/templates/product.jinja2:
--------------------------------------------------------------------------------
1 | {% extends "layout.jinja2" %}
2 |
3 | {% block pagestyles %}
4 | {% assets "product_style_bundle" %}
5 |
6 | {% endassets %}
7 | {% endblock %}
8 |
9 | {% block content %}
10 | {% include "navigation.jinja2" %}
11 |
12 |
13 |
{{ product.name }}
14 |
15 |

16 |
17 |
Price: ${{ product.salePrice }}
18 |
Rating: {{ product.customerReviewAverage }}/5
19 |
# Reviews: {{ product.customerReviewCount }}
20 |
SKU: {{ product.sku }}
21 |
{{ product.longDescription }}
22 |
23 |
24 |
25 |
26 |
27 | {% include "blueprintinfo.jinja2" %}
28 |
29 | {% endblock %}
30 |
--------------------------------------------------------------------------------
/flask_blueprint_tutorial/profile/profile.py:
--------------------------------------------------------------------------------
1 | """Routes for logged-in profile."""
2 | from faker import Faker
3 | from flask import Blueprint, render_template
4 |
5 | fake = Faker()
6 |
7 | # Blueprint Configuration
8 | profile_blueprint = Blueprint("profile_blueprint", __name__, template_folder="templates", static_folder="static")
9 |
10 |
11 | @profile_blueprint.route("/profile", methods=["GET"])
12 | def user_profile() -> str:
13 | """
14 | Logged-in user profile page.
15 |
16 | :returns: str
17 | """
18 | user = fake.simple_profile()
19 | job = fake.job()
20 | return render_template(
21 | "profile.jinja2",
22 | title="User Profile",
23 | template="profile-template",
24 | user=user,
25 | job=job,
26 | )
27 |
--------------------------------------------------------------------------------
/flask_blueprint_tutorial/profile/static/img/avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackersandslackers/flask-blueprint-tutorial/44120992b8507b140eedcf5fc78870a8a09130be/flask_blueprint_tutorial/profile/static/img/avatar.png
--------------------------------------------------------------------------------
/flask_blueprint_tutorial/profile/static/less/profile.less:
--------------------------------------------------------------------------------
1 | .profile-template {
2 | .account-header {
3 | display: flex;
4 |
5 | .user-meta {
6 | margin-top: 10px;
7 | }
8 |
9 | .username {
10 | line-height: 1;
11 | margin-bottom: 10px;
12 | }
13 |
14 | .user-avatar {
15 | height: 152px;
16 | border-radius: 3px;
17 | margin-right: 25px;
18 | }
19 |
20 | .user-name {
21 | font-size: 1.4em;
22 | }
23 |
24 | .meta-name {
25 | font-weight: 600;
26 | width: 88px;
27 | display: inline-block;
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/flask_blueprint_tutorial/profile/templates/profile.jinja2:
--------------------------------------------------------------------------------
1 | {% extends "layout.jinja2" %}
2 |
3 | {% block pagestyles %}
4 | {% assets "profile_style_bundle" %}
5 |
6 | {% endassets %}
7 | {% endblock %}
8 |
9 | {% block content %}
10 | {% include "navigation.jinja2" %}
11 |
12 |
13 |
29 |
{{ user.bio }}
30 |
31 |
32 | {% include "blueprintinfo.jinja2" %}
33 |
34 | {% endblock %}
35 |
--------------------------------------------------------------------------------
/flask_blueprint_tutorial/static/dist/css/home.css:
--------------------------------------------------------------------------------
1 | .home-template.page .products{display:none!important}.home-template .container .resource-links{margin-top:10px}.home-template .container .resource-links .resource-link{margin:4px 0;color:#8a91a7;line-height:1}.home-template .container .products{display:flex;justify-content:space-between;flex-wrap:wrap;margin-top:30px}.home-template .container .products .product-preview{padding:2%;box-shadow:0 0 5px rgba(65,67,144,0.15);max-width:28%;width:30%;background:white;margin-bottom:20px;display:block;text-align:center;overflow:hidden;transition:all .2s ease-out}.home-template .container .products .product-preview:hover{background:#5dbad7;color:white!important;opacity:1}.home-template .container .products .product-preview:hover *{color:white!important}.home-template .container .products .product-preview .product-image{height:95px;margin:0 auto 15px;display:block}@media(max-width:600px){.home-template .container .products .product-preview .product-image{width:90%;height:unset}}.home-template .container .products .product-preview .name{-webkit-line-clamp:2;-webkit-box-orient:vertical;display:-webkit-box;width:-webkit-fill-available;width:-moz-available;width:stretch;overflow:hidden;text-overflow:ellipsis;font-weight:500;font-size:1em;line-height:1.3;height:60px}.home-template .container .products .product-preview .price{color:#5f6988;margin-top:5px;font-size:1.1em;font-weight:600}.home-template h2{margin-bottom:0!important}
--------------------------------------------------------------------------------
/flask_blueprint_tutorial/static/dist/css/product.css:
--------------------------------------------------------------------------------
1 | .product-template .product-name{font-size:1.9em;margin-bottom:30px!important;line-height:1.25em}.product-template .product-image{height:250px;max-width:30vw;margin-right:40px;border-radius:3px}.product-template .product-details{display:grid;grid-template-columns:1fr 2fr}.product-template .product-details .product-info{display:flex;justify-content:space-between;flex-direction:column}.product-template .meta{font-size:1.1em}.product-template .meta .meta-name{font-weight:600;width:114px;display:inline-block}.product-template p{font-size:.85em;line-height:1.5}.product-template button{background:#5eb9d7;color:white;border-radius:2px;margin-top:10px;font-weight:400;border:1px solid #5eb9d7;transition:all .3s ease-out;padding:10px;font-size:1em;font-weight:bold;max-width:283px}.product-template button:hover{cursor:pointer;background:white;color:#5eb9d7}
--------------------------------------------------------------------------------
/flask_blueprint_tutorial/static/dist/css/profile.css:
--------------------------------------------------------------------------------
1 | .profile-template .account-header{display:flex}.profile-template .account-header .user-meta{margin-top:10px}.profile-template .account-header .username{line-height:1;margin-bottom:10px}.profile-template .account-header .user-avatar{height:152px;border-radius:3px;margin-right:25px}.profile-template .account-header .user-name{font-size:1.4em}.profile-template .account-header .meta-name{font-weight:600;width:88px;display:inline-block}
--------------------------------------------------------------------------------
/flask_blueprint_tutorial/static/dist/css/style.css:
--------------------------------------------------------------------------------
1 | nav{background:#fff;padding:20px 0;margin-bottom:40px;box-shadow:0 0 5px #bec6cf}nav .nav-wrapper{width:985px!important;max-width:88%!important;display:flex;justify-content:space-between;align-items:center;margin:0 auto!important}nav .nav-wrapper .left-nav{display:flex;justify-content:space-between;align-items:center}nav .nav-wrapper .left-nav a{margin-right:40px}@media(max-width:600px){nav .nav-wrapper .left-nav a{margin-right:25px!important}}nav .nav-wrapper .left-nav img{width:50px;margin-right:0}nav .nav-wrapper a{font-weight:600;color:#7d849a}@media(max-width:600px){nav .nav-wrapper a{font-size:.95em}}nav a{color:#4d545d;transition:all .2s ease-out;text-decoration:none;font-size:1.1em}nav a:hover{cursor:pointer;opacity:.7}nav{background:#fff;padding:30px;width:auto;margin-bottom:40px;box-shadow:0 0 5px #bec6cf}nav .nav-wrapper{max-width:1014px!important;display:flex;justify-content:space-between;align-items:center;width:auto;margin:0 auto}nav .nav-wrapper .left-nav{display:flex;justify-content:space-between;align-items:center}nav .nav-wrapper .left-nav a{margin-right:40px}@media(max-width:600px){nav .nav-wrapper .left-nav a{margin-right:25px!important}}nav .nav-wrapper .left-nav img{width:50px;margin-right:0}nav .nav-wrapper a{font-weight:600;color:#7d849a}@media(max-width:600px){nav .nav-wrapper a{font-size:.95em}}nav a{color:#4d545d;transition:all .2s ease-out;text-decoration:none;font-size:1.1em}nav a:hover{cursor:pointer;opacity:.7}body,html{font-family:'Poppins',sans-serif;margin:0;padding:0;background:#f0f0f0;height:100%;color:#5f6988}body .container,html .container{width:948px;max-width:88%;margin:0 auto;height:fit-content;background:white;padding:30px 40px;box-shadow:0 0 5px rgba(65,67,144,0.15)}@media(max-width:800px){body .container,html .container{width:82%;margin:0 auto 20px;padding:30px 6%}}body .container h1,html .container h1{line-height:1.5;margin:0 0 5px}body .container h2,html .container h2{margin:0;font-weight:400;font-size:1.2em;line-height:1}body .container a,html .container a{color:#5dbad7;transition:all .2s ease-out;text-decoration:none}body .container a:hover,html .container a:hover{cursor:pointer;opacity:.7}body .blueprint-info,html .blueprint-info{height:fit-content;margin:20px auto 0;width:968px;max-width:88%;background:white;padding:20px 30px;box-shadow:0 0 5px rgba(65,67,144,0.15)}@media(max-width:800px){body .blueprint-info,html .blueprint-info{width:82%;margin:0 auto;padding:30px 6%}}body .blueprint-info h3,html .blueprint-info h3{margin:0;font-size:1.4em;font-weight:500}body .blueprint-info ul,html .blueprint-info ul{list-style:none;border:1px solid #e6e6e6;padding:10px}body .blueprint-info ul li,html .blueprint-info ul li{display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid #ececec;padding-bottom:10px;margin-bottom:10px;font-size:1.1em}body .blueprint-info ul li:last-of-type,html .blueprint-info ul li:last-of-type{border-bottom:none;margin-bottom:0;padding-bottom:0}body .blueprint-info ul li span,html .blueprint-info ul li span{font-weight:500;font-size:.9em}body .blueprint-info ul .attribute-value,html .blueprint-info ul .attribute-value{font-weight:300;font-size:.9em}
--------------------------------------------------------------------------------
/flask_blueprint_tutorial/static/dist/img/avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackersandslackers/flask-blueprint-tutorial/44120992b8507b140eedcf5fc78870a8a09130be/flask_blueprint_tutorial/static/dist/img/avatar.png
--------------------------------------------------------------------------------
/flask_blueprint_tutorial/static/dist/img/favicon@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackersandslackers/flask-blueprint-tutorial/44120992b8507b140eedcf5fc78870a8a09130be/flask_blueprint_tutorial/static/dist/img/favicon@2x.png
--------------------------------------------------------------------------------
/flask_blueprint_tutorial/static/dist/img/logo@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackersandslackers/flask-blueprint-tutorial/44120992b8507b140eedcf5fc78870a8a09130be/flask_blueprint_tutorial/static/dist/img/logo@2x.png
--------------------------------------------------------------------------------
/flask_blueprint_tutorial/static/src/less/nav.less:
--------------------------------------------------------------------------------
1 | @import 'variables.less';
2 |
3 | nav {
4 | background: #fff;
5 | padding: 20px 0;
6 | margin-bottom: 40px;
7 | box-shadow: 0 0 5px #bec6cf;
8 |
9 | .nav-wrapper {
10 | width: 985px !important;
11 | max-width: 88% !important;
12 | display: flex;
13 | justify-content: space-between;
14 | align-items: center;
15 | margin: 0 auto !important;
16 |
17 | .left-nav {
18 | display: flex;
19 | justify-content: space-between;
20 | align-items: center;
21 |
22 | a {
23 | margin-right: 40px;
24 | @media (max-width: 600px) {
25 | margin-right: 25px !important;
26 | }
27 | }
28 |
29 | img {
30 | width: 50px;
31 | margin-right: 0;
32 | }
33 | }
34 |
35 | a {
36 | font-weight: 600;
37 | color: #7d849a;
38 | @media (max-width: 600px) {
39 | font-size: .95em;
40 | }
41 | }
42 | }
43 |
44 | a {
45 | color: #4d545d;
46 | transition: @transition;
47 | text-decoration: none;
48 | font-size: 1.1em;
49 |
50 | &:hover {
51 | cursor: pointer;
52 | opacity: .7;
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/flask_blueprint_tutorial/static/src/less/style.less:
--------------------------------------------------------------------------------
1 | @import 'variables.less';
2 | @import 'nav.less';
3 |
4 | body,
5 | html {
6 | font-family: @body-font;
7 | margin: 0;
8 | padding: 0;
9 | background: #f0f0f0;
10 | height: 100%;
11 | color: #5f6988;
12 |
13 | .container {
14 | width: 948px;
15 | max-width: 88%;
16 | margin: 0 auto;
17 | height: fit-content;
18 | background: white;
19 | padding: 30px 40px;
20 | box-shadow: 0 0 5px rgba(65, 67, 144, 0.15);
21 | @media (max-width: 800px) {
22 | width: 82%;
23 | margin: 0 auto 20px;
24 | padding: 30px 6%;
25 | }
26 |
27 | h1 {
28 | line-height: 1.5;
29 | margin: 0 0 5px;
30 | }
31 |
32 | h2 {
33 | margin: 0;
34 | font-weight: 400;
35 | font-size: 1.2em;
36 | line-height: 1;
37 | }
38 |
39 | a {
40 | color: #5dbad7;
41 | transition: @transition;
42 | text-decoration: none;
43 |
44 | &:hover {
45 | cursor: pointer;
46 | opacity: .7;
47 | }
48 | }
49 | }
50 |
51 | .blueprint-info {
52 | height: fit-content;
53 | margin: 20px auto 0;
54 | width: 968px;
55 | max-width: 88%;
56 | background: white;
57 | padding: 20px 30px;
58 | box-shadow: 0 0 5px rgba(65, 67, 144, 0.15);
59 | @media (max-width: 800px) {
60 | width: 82%;
61 | margin: 0 auto;
62 | padding: 30px 6%;
63 | }
64 |
65 | h3 {
66 | margin: 0;
67 | font-size: 1.4em;
68 | font-weight: 500;
69 | }
70 |
71 | ul {
72 | list-style: none;
73 | border: 1px solid #e6e6e6;
74 | padding: 10px;
75 |
76 | li {
77 | display: flex;
78 | justify-content: space-between;
79 | align-items: center;
80 | border-bottom: 1px solid #ececec;
81 | padding-bottom: 10px;
82 | margin-bottom: 10px;
83 | font-size: 1.1em;
84 |
85 | &:last-of-type {
86 | border-bottom: none;
87 | margin-bottom: 0;
88 | padding-bottom: 0;
89 | }
90 |
91 | span {
92 | font-weight: 500;
93 | font-size: .9em;
94 | }
95 | }
96 |
97 | .attribute-value {
98 | font-weight: 300;
99 | font-size: .9em;
100 | }
101 | }
102 | }
103 | }
104 |
105 |
106 |
--------------------------------------------------------------------------------
/flask_blueprint_tutorial/static/src/less/variables.less:
--------------------------------------------------------------------------------
1 | // Colors
2 | @theme-color: #5eb9d7;
3 | @background-color: #e1eaf5;
4 | @box-shadow: 0 0 5px rgba(65, 67, 144, 0.15);
5 | @header-color: #5f6988;
6 |
7 | // Fonts
8 | @body-font: 'Poppins', sans-serif;
9 |
10 | // Animation
11 | @transition: all .2s ease-out;
12 |
--------------------------------------------------------------------------------
/flask_blueprint_tutorial/templates/analytics.jinja2:
--------------------------------------------------------------------------------
1 |
15 |
--------------------------------------------------------------------------------
/flask_blueprint_tutorial/templates/blueprintinfo.jinja2:
--------------------------------------------------------------------------------
1 |
2 |
Blueprint Info
3 |
4 | - Blueprint: {{ request.blueprint }}
5 | - Template: {{ self._TemplateReference__context.name }}
6 | - View: {{ request.endpoint }}
7 | - Route: {{ request.path }}
8 |
9 |
--------------------------------------------------------------------------------
/flask_blueprint_tutorial/templates/layout.jinja2:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {{ title }} | Flask Blueprint Tutorial
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | {% block pagestyles %}{% endblock %}
32 |
33 |
34 |
35 | {% block content %}{% endblock %}
36 | {% include 'analytics.jinja2' %}
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/flask_blueprint_tutorial/templates/navigation.jinja2:
--------------------------------------------------------------------------------
1 |
23 |
--------------------------------------------------------------------------------
/mypy.ini:
--------------------------------------------------------------------------------
1 | [mypy]
2 | python_version = 3.10
3 | warn_return_any = True
4 | warn_unused_configs = True
--------------------------------------------------------------------------------
/poetry.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand.
2 |
3 | [[package]]
4 | name = "black"
5 | version = "23.12.1"
6 | description = "The uncompromising code formatter."
7 | optional = false
8 | python-versions = ">=3.8"
9 | files = [
10 | {file = "black-23.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0aaf6041986767a5e0ce663c7a2f0e9eaf21e6ff87a5f95cbf3675bfd4c41d2"},
11 | {file = "black-23.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c88b3711d12905b74206227109272673edce0cb29f27e1385f33b0163c414bba"},
12 | {file = "black-23.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920b569dc6b3472513ba6ddea21f440d4b4c699494d2e972a1753cdc25df7b0"},
13 | {file = "black-23.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:3fa4be75ef2a6b96ea8d92b1587dd8cb3a35c7e3d51f0738ced0781c3aa3a5a3"},
14 | {file = "black-23.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8d4df77958a622f9b5a4c96edb4b8c0034f8434032ab11077ec6c56ae9f384ba"},
15 | {file = "black-23.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:602cfb1196dc692424c70b6507593a2b29aac0547c1be9a1d1365f0d964c353b"},
16 | {file = "black-23.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c4352800f14be5b4864016882cdba10755bd50805c95f728011bcb47a4afd59"},
17 | {file = "black-23.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:0808494f2b2df923ffc5723ed3c7b096bd76341f6213989759287611e9837d50"},
18 | {file = "black-23.12.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:25e57fd232a6d6ff3f4478a6fd0580838e47c93c83eaf1ccc92d4faf27112c4e"},
19 | {file = "black-23.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d9e13db441c509a3763a7a3d9a49ccc1b4e974a47be4e08ade2a228876500ec"},
20 | {file = "black-23.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1bd9c210f8b109b1762ec9fd36592fdd528485aadb3f5849b2740ef17e674e"},
21 | {file = "black-23.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:ae76c22bde5cbb6bfd211ec343ded2163bba7883c7bc77f6b756a1049436fbb9"},
22 | {file = "black-23.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1fa88a0f74e50e4487477bc0bb900c6781dbddfdfa32691e780bf854c3b4a47f"},
23 | {file = "black-23.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4d6a9668e45ad99d2f8ec70d5c8c04ef4f32f648ef39048d010b0689832ec6d"},
24 | {file = "black-23.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b18fb2ae6c4bb63eebe5be6bd869ba2f14fd0259bda7d18a46b764d8fb86298a"},
25 | {file = "black-23.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:c04b6d9d20e9c13f43eee8ea87d44156b8505ca8a3c878773f68b4e4812a421e"},
26 | {file = "black-23.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e1b38b3135fd4c025c28c55ddfc236b05af657828a8a6abe5deec419a0b7055"},
27 | {file = "black-23.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4f0031eaa7b921db76decd73636ef3a12c942ed367d8c3841a0739412b260a54"},
28 | {file = "black-23.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97e56155c6b737854e60a9ab1c598ff2533d57e7506d97af5481141671abf3ea"},
29 | {file = "black-23.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:dd15245c8b68fe2b6bd0f32c1556509d11bb33aec9b5d0866dd8e2ed3dba09c2"},
30 | {file = "black-23.12.1-py3-none-any.whl", hash = "sha256:78baad24af0f033958cad29731e27363183e140962595def56423e626f4bee3e"},
31 | {file = "black-23.12.1.tar.gz", hash = "sha256:4ce3ef14ebe8d9509188014d96af1c456a910d5b5cbf434a09fef7e024b3d0d5"},
32 | ]
33 |
34 | [package.dependencies]
35 | click = ">=8.0.0"
36 | mypy-extensions = ">=0.4.3"
37 | packaging = ">=22.0"
38 | pathspec = ">=0.9.0"
39 | platformdirs = ">=2"
40 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
41 | typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""}
42 |
43 | [package.extras]
44 | colorama = ["colorama (>=0.4.3)"]
45 | d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"]
46 | jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
47 | uvloop = ["uvloop (>=0.15.2)"]
48 |
49 | [[package]]
50 | name = "blinker"
51 | version = "1.7.0"
52 | description = "Fast, simple object-to-object and broadcast signaling"
53 | optional = false
54 | python-versions = ">=3.8"
55 | files = [
56 | {file = "blinker-1.7.0-py3-none-any.whl", hash = "sha256:c3f865d4d54db7abc53758a01601cf343fe55b84c1de4e3fa910e420b438d5b9"},
57 | {file = "blinker-1.7.0.tar.gz", hash = "sha256:e6820ff6fa4e4d1d8e2747c2283749c3f547e4fee112b98555cdcdae32996182"},
58 | ]
59 |
60 | [[package]]
61 | name = "click"
62 | version = "8.1.7"
63 | description = "Composable command line interface toolkit"
64 | optional = false
65 | python-versions = ">=3.7"
66 | files = [
67 | {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
68 | {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
69 | ]
70 |
71 | [package.dependencies]
72 | colorama = {version = "*", markers = "platform_system == \"Windows\""}
73 |
74 | [[package]]
75 | name = "colorama"
76 | version = "0.4.6"
77 | description = "Cross-platform colored terminal text."
78 | optional = false
79 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
80 | files = [
81 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
82 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
83 | ]
84 |
85 | [[package]]
86 | name = "cssmin"
87 | version = "0.2.0"
88 | description = "A Python port of the YUI CSS compression algorithm."
89 | optional = false
90 | python-versions = "*"
91 | files = [
92 | {file = "cssmin-0.2.0.tar.gz", hash = "sha256:e012f0cc8401efcf2620332339011564738ae32be8c84b2e43ce8beaec1067b6"},
93 | ]
94 |
95 | [[package]]
96 | name = "faker"
97 | version = "21.0.0"
98 | description = "Faker is a Python package that generates fake data for you."
99 | optional = false
100 | python-versions = ">=3.8"
101 | files = [
102 | {file = "Faker-21.0.0-py3-none-any.whl", hash = "sha256:ff61cca42547795bee8a11319792a8fee6d0f0cd191e831f7f3050c5851fcd8a"},
103 | {file = "Faker-21.0.0.tar.gz", hash = "sha256:2d8a350e952225a145307d7461881c44a1c9320e90fbe8bd903d5947f133f3ec"},
104 | ]
105 |
106 | [package.dependencies]
107 | python-dateutil = ">=2.4"
108 |
109 | [[package]]
110 | name = "flake8"
111 | version = "6.1.0"
112 | description = "the modular source code checker: pep8 pyflakes and co"
113 | optional = false
114 | python-versions = ">=3.8.1"
115 | files = [
116 | {file = "flake8-6.1.0-py2.py3-none-any.whl", hash = "sha256:ffdfce58ea94c6580c77888a86506937f9a1a227dfcd15f245d694ae20a6b6e5"},
117 | {file = "flake8-6.1.0.tar.gz", hash = "sha256:d5b3857f07c030bdb5bf41c7f53799571d75c4491748a3adcd47de929e34cd23"},
118 | ]
119 |
120 | [package.dependencies]
121 | mccabe = ">=0.7.0,<0.8.0"
122 | pycodestyle = ">=2.11.0,<2.12.0"
123 | pyflakes = ">=3.1.0,<3.2.0"
124 |
125 | [[package]]
126 | name = "flask"
127 | version = "3.0.0"
128 | description = "A simple framework for building complex web applications."
129 | optional = false
130 | python-versions = ">=3.8"
131 | files = [
132 | {file = "flask-3.0.0-py3-none-any.whl", hash = "sha256:21128f47e4e3b9d597a3e8521a329bf56909b690fcc3fa3e477725aa81367638"},
133 | {file = "flask-3.0.0.tar.gz", hash = "sha256:cfadcdb638b609361d29ec22360d6070a77d7463dcb3ab08d2c2f2f168845f58"},
134 | ]
135 |
136 | [package.dependencies]
137 | blinker = ">=1.6.2"
138 | click = ">=8.1.3"
139 | itsdangerous = ">=2.1.2"
140 | Jinja2 = ">=3.1.2"
141 | Werkzeug = ">=3.0.0"
142 |
143 | [package.extras]
144 | async = ["asgiref (>=3.2)"]
145 | dotenv = ["python-dotenv"]
146 |
147 | [[package]]
148 | name = "flask-assets"
149 | version = "2.1.0"
150 | description = "Asset management for Flask, to compress and merge CSS and Javascript files."
151 | optional = false
152 | python-versions = "*"
153 | files = [
154 | {file = "Flask-Assets-2.1.0.tar.gz", hash = "sha256:f84d6532ffe59c9ff352885e8740ff4da25c0bcfacd805f0a806815e44354813"},
155 | {file = "Flask_Assets-2.1.0-py3-none-any.whl", hash = "sha256:a56c476b15f84701712cc3b4b4a001ebbe62b1dcbe81c23f96fbe6f261b75324"},
156 | ]
157 |
158 | [package.dependencies]
159 | Flask = ">=0.8"
160 | webassets = ">=2.0"
161 |
162 | [[package]]
163 | name = "gunicorn"
164 | version = "21.2.0"
165 | description = "WSGI HTTP Server for UNIX"
166 | optional = false
167 | python-versions = ">=3.5"
168 | files = [
169 | {file = "gunicorn-21.2.0-py3-none-any.whl", hash = "sha256:3213aa5e8c24949e792bcacfc176fef362e7aac80b76c56f6b5122bf350722f0"},
170 | {file = "gunicorn-21.2.0.tar.gz", hash = "sha256:88ec8bff1d634f98e61b9f65bc4bf3cd918a90806c6f5c48bc5603849ec81033"},
171 | ]
172 |
173 | [package.dependencies]
174 | packaging = "*"
175 |
176 | [package.extras]
177 | eventlet = ["eventlet (>=0.24.1)"]
178 | gevent = ["gevent (>=1.4.0)"]
179 | setproctitle = ["setproctitle"]
180 | tornado = ["tornado (>=0.2)"]
181 |
182 | [[package]]
183 | name = "isort"
184 | version = "5.13.2"
185 | description = "A Python utility / library to sort Python imports."
186 | optional = false
187 | python-versions = ">=3.8.0"
188 | files = [
189 | {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"},
190 | {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"},
191 | ]
192 |
193 | [package.extras]
194 | colors = ["colorama (>=0.4.6)"]
195 |
196 | [[package]]
197 | name = "itsdangerous"
198 | version = "2.1.2"
199 | description = "Safely pass data to untrusted environments and back."
200 | optional = false
201 | python-versions = ">=3.7"
202 | files = [
203 | {file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"},
204 | {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"},
205 | ]
206 |
207 | [[package]]
208 | name = "jinja2"
209 | version = "3.1.2"
210 | description = "A very fast and expressive template engine."
211 | optional = false
212 | python-versions = ">=3.7"
213 | files = [
214 | {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"},
215 | {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"},
216 | ]
217 |
218 | [package.dependencies]
219 | MarkupSafe = ">=2.0"
220 |
221 | [package.extras]
222 | i18n = ["Babel (>=2.7)"]
223 |
224 | [[package]]
225 | name = "jsmin"
226 | version = "3.0.1"
227 | description = "JavaScript minifier."
228 | optional = false
229 | python-versions = "*"
230 | files = [
231 | {file = "jsmin-3.0.1.tar.gz", hash = "sha256:c0959a121ef94542e807a674142606f7e90214a2b3d1eb17300244bbb5cc2bfc"},
232 | ]
233 |
234 | [[package]]
235 | name = "lesscpy"
236 | version = "0.15.1"
237 | description = "Python LESS compiler"
238 | optional = false
239 | python-versions = "*"
240 | files = [
241 | {file = "lesscpy-0.15.1-py2.py3-none-any.whl", hash = "sha256:8d26e58ed4812b345c2896daea435a28cb3182f87ae3391157085255d4c37dff"},
242 | {file = "lesscpy-0.15.1.tar.gz", hash = "sha256:1045d17a98f688646ca758dff254e6e9c03745648e051a081b0395c3b77c824c"},
243 | ]
244 |
245 | [package.dependencies]
246 | ply = "*"
247 |
248 | [[package]]
249 | name = "markupsafe"
250 | version = "2.1.3"
251 | description = "Safely add untrusted strings to HTML/XML markup."
252 | optional = false
253 | python-versions = ">=3.7"
254 | files = [
255 | {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"},
256 | {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"},
257 | {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"},
258 | {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"},
259 | {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"},
260 | {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"},
261 | {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"},
262 | {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"},
263 | {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"},
264 | {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"},
265 | {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"},
266 | {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"},
267 | {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"},
268 | {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"},
269 | {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"},
270 | {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"},
271 | {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"},
272 | {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"},
273 | {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"},
274 | {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"},
275 | {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"},
276 | {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"},
277 | {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"},
278 | {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"},
279 | {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"},
280 | {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"},
281 | {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"},
282 | {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"},
283 | {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"},
284 | {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"},
285 | {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"},
286 | {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"},
287 | {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"},
288 | {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"},
289 | {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"},
290 | {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"},
291 | {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"},
292 | {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"},
293 | {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"},
294 | {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"},
295 | {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"},
296 | {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"},
297 | {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"},
298 | {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"},
299 | {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"},
300 | {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"},
301 | {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"},
302 | {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"},
303 | {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"},
304 | {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"},
305 | {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"},
306 | {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"},
307 | {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"},
308 | {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"},
309 | {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"},
310 | {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"},
311 | {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"},
312 | {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"},
313 | {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"},
314 | {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"},
315 | ]
316 |
317 | [[package]]
318 | name = "mccabe"
319 | version = "0.7.0"
320 | description = "McCabe checker, plugin for flake8"
321 | optional = false
322 | python-versions = ">=3.6"
323 | files = [
324 | {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
325 | {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
326 | ]
327 |
328 | [[package]]
329 | name = "mypy"
330 | version = "1.8.0"
331 | description = "Optional static typing for Python"
332 | optional = false
333 | python-versions = ">=3.8"
334 | files = [
335 | {file = "mypy-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:485a8942f671120f76afffff70f259e1cd0f0cfe08f81c05d8816d958d4577d3"},
336 | {file = "mypy-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:df9824ac11deaf007443e7ed2a4a26bebff98d2bc43c6da21b2b64185da011c4"},
337 | {file = "mypy-1.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2afecd6354bbfb6e0160f4e4ad9ba6e4e003b767dd80d85516e71f2e955ab50d"},
338 | {file = "mypy-1.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8963b83d53ee733a6e4196954502b33567ad07dfd74851f32be18eb932fb1cb9"},
339 | {file = "mypy-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:e46f44b54ebddbeedbd3d5b289a893219065ef805d95094d16a0af6630f5d410"},
340 | {file = "mypy-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:855fe27b80375e5c5878492f0729540db47b186509c98dae341254c8f45f42ae"},
341 | {file = "mypy-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c886c6cce2d070bd7df4ec4a05a13ee20c0aa60cb587e8d1265b6c03cf91da3"},
342 | {file = "mypy-1.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d19c413b3c07cbecf1f991e2221746b0d2a9410b59cb3f4fb9557f0365a1a817"},
343 | {file = "mypy-1.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9261ed810972061388918c83c3f5cd46079d875026ba97380f3e3978a72f503d"},
344 | {file = "mypy-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:51720c776d148bad2372ca21ca29256ed483aa9a4cdefefcef49006dff2a6835"},
345 | {file = "mypy-1.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:52825b01f5c4c1c4eb0db253ec09c7aa17e1a7304d247c48b6f3599ef40db8bd"},
346 | {file = "mypy-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55"},
347 | {file = "mypy-1.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afe3fe972c645b4632c563d3f3eff1cdca2fa058f730df2b93a35e3b0c538218"},
348 | {file = "mypy-1.8.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:42c6680d256ab35637ef88891c6bd02514ccb7e1122133ac96055ff458f93fc3"},
349 | {file = "mypy-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:720a5ca70e136b675af3af63db533c1c8c9181314d207568bbe79051f122669e"},
350 | {file = "mypy-1.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:028cf9f2cae89e202d7b6593cd98db6759379f17a319b5faf4f9978d7084cdc6"},
351 | {file = "mypy-1.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4e6d97288757e1ddba10dd9549ac27982e3e74a49d8d0179fc14d4365c7add66"},
352 | {file = "mypy-1.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f1478736fcebb90f97e40aff11a5f253af890c845ee0c850fe80aa060a267c6"},
353 | {file = "mypy-1.8.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42419861b43e6962a649068a61f4a4839205a3ef525b858377a960b9e2de6e0d"},
354 | {file = "mypy-1.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:2b5b6c721bd4aabaadead3a5e6fa85c11c6c795e0c81a7215776ef8afc66de02"},
355 | {file = "mypy-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5c1538c38584029352878a0466f03a8ee7547d7bd9f641f57a0f3017a7c905b8"},
356 | {file = "mypy-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ef4be7baf08a203170f29e89d79064463b7fc7a0908b9d0d5114e8009c3a259"},
357 | {file = "mypy-1.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7178def594014aa6c35a8ff411cf37d682f428b3b5617ca79029d8ae72f5402b"},
358 | {file = "mypy-1.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ab3c84fa13c04aeeeabb2a7f67a25ef5d77ac9d6486ff33ded762ef353aa5592"},
359 | {file = "mypy-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:99b00bc72855812a60d253420d8a2eae839b0afa4938f09f4d2aa9bb4654263a"},
360 | {file = "mypy-1.8.0-py3-none-any.whl", hash = "sha256:538fd81bb5e430cc1381a443971c0475582ff9f434c16cd46d2c66763ce85d9d"},
361 | {file = "mypy-1.8.0.tar.gz", hash = "sha256:6ff8b244d7085a0b425b56d327b480c3b29cafbd2eff27316a004f9a7391ae07"},
362 | ]
363 |
364 | [package.dependencies]
365 | mypy-extensions = ">=1.0.0"
366 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
367 | typing-extensions = ">=4.1.0"
368 |
369 | [package.extras]
370 | dmypy = ["psutil (>=4.0)"]
371 | install-types = ["pip"]
372 | mypyc = ["setuptools (>=50)"]
373 | reports = ["lxml"]
374 |
375 | [[package]]
376 | name = "mypy-extensions"
377 | version = "1.0.0"
378 | description = "Type system extensions for programs checked with the mypy type checker."
379 | optional = false
380 | python-versions = ">=3.5"
381 | files = [
382 | {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
383 | {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
384 | ]
385 |
386 | [[package]]
387 | name = "packaging"
388 | version = "23.2"
389 | description = "Core utilities for Python packages"
390 | optional = false
391 | python-versions = ">=3.7"
392 | files = [
393 | {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"},
394 | {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"},
395 | ]
396 |
397 | [[package]]
398 | name = "pathspec"
399 | version = "0.12.1"
400 | description = "Utility library for gitignore style pattern matching of file paths."
401 | optional = false
402 | python-versions = ">=3.8"
403 | files = [
404 | {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
405 | {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
406 | ]
407 |
408 | [[package]]
409 | name = "platformdirs"
410 | version = "4.1.0"
411 | description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
412 | optional = false
413 | python-versions = ">=3.8"
414 | files = [
415 | {file = "platformdirs-4.1.0-py3-none-any.whl", hash = "sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380"},
416 | {file = "platformdirs-4.1.0.tar.gz", hash = "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420"},
417 | ]
418 |
419 | [package.extras]
420 | docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"]
421 | test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"]
422 |
423 | [[package]]
424 | name = "ply"
425 | version = "3.11"
426 | description = "Python Lex & Yacc"
427 | optional = false
428 | python-versions = "*"
429 | files = [
430 | {file = "ply-3.11-py2.py3-none-any.whl", hash = "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce"},
431 | {file = "ply-3.11.tar.gz", hash = "sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3"},
432 | ]
433 |
434 | [[package]]
435 | name = "pycodestyle"
436 | version = "2.11.1"
437 | description = "Python style guide checker"
438 | optional = false
439 | python-versions = ">=3.8"
440 | files = [
441 | {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"},
442 | {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"},
443 | ]
444 |
445 | [[package]]
446 | name = "pyflakes"
447 | version = "3.1.0"
448 | description = "passive checker of Python programs"
449 | optional = false
450 | python-versions = ">=3.8"
451 | files = [
452 | {file = "pyflakes-3.1.0-py2.py3-none-any.whl", hash = "sha256:4132f6d49cb4dae6819e5379898f2b8cce3c5f23994194c24b77d5da2e36f774"},
453 | {file = "pyflakes-3.1.0.tar.gz", hash = "sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc"},
454 | ]
455 |
456 | [[package]]
457 | name = "python-dateutil"
458 | version = "2.8.2"
459 | description = "Extensions to the standard Python datetime module"
460 | optional = false
461 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
462 | files = [
463 | {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
464 | {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
465 | ]
466 |
467 | [package.dependencies]
468 | six = ">=1.5"
469 |
470 | [[package]]
471 | name = "python-dotenv"
472 | version = "1.0.0"
473 | description = "Read key-value pairs from a .env file and set them as environment variables"
474 | optional = false
475 | python-versions = ">=3.8"
476 | files = [
477 | {file = "python-dotenv-1.0.0.tar.gz", hash = "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba"},
478 | {file = "python_dotenv-1.0.0-py3-none-any.whl", hash = "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a"},
479 | ]
480 |
481 | [package.extras]
482 | cli = ["click (>=5.0)"]
483 |
484 | [[package]]
485 | name = "six"
486 | version = "1.16.0"
487 | description = "Python 2 and 3 compatibility utilities"
488 | optional = false
489 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
490 | files = [
491 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
492 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
493 | ]
494 |
495 | [[package]]
496 | name = "tomli"
497 | version = "2.0.1"
498 | description = "A lil' TOML parser"
499 | optional = false
500 | python-versions = ">=3.7"
501 | files = [
502 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
503 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
504 | ]
505 |
506 | [[package]]
507 | name = "typing-extensions"
508 | version = "4.9.0"
509 | description = "Backported and Experimental Type Hints for Python 3.8+"
510 | optional = false
511 | python-versions = ">=3.8"
512 | files = [
513 | {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"},
514 | {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"},
515 | ]
516 |
517 | [[package]]
518 | name = "webassets"
519 | version = "2.0"
520 | description = "Media asset management for Python, with glue code for various web frameworks"
521 | optional = false
522 | python-versions = "*"
523 | files = [
524 | {file = "webassets-2.0-py3-none-any.whl", hash = "sha256:a31a55147752ba1b3dc07dee0ad8c8efff274464e08bbdb88c1fd59ffd552724"},
525 | {file = "webassets-2.0.tar.gz", hash = "sha256:167132337677c8cedc9705090f6d48da3fb262c8e0b2773b29f3352f050181cd"},
526 | ]
527 |
528 | [[package]]
529 | name = "werkzeug"
530 | version = "3.0.1"
531 | description = "The comprehensive WSGI web application library."
532 | optional = false
533 | python-versions = ">=3.8"
534 | files = [
535 | {file = "werkzeug-3.0.1-py3-none-any.whl", hash = "sha256:90a285dc0e42ad56b34e696398b8122ee4c681833fb35b8334a095d82c56da10"},
536 | {file = "werkzeug-3.0.1.tar.gz", hash = "sha256:507e811ecea72b18a404947aded4b3390e1db8f826b494d76550ef45bb3b1dcc"},
537 | ]
538 |
539 | [package.dependencies]
540 | MarkupSafe = ">=2.1.1"
541 |
542 | [package.extras]
543 | watchdog = ["watchdog (>=2.3)"]
544 |
545 | [metadata]
546 | lock-version = "2.0"
547 | python-versions = "^3.10,<4"
548 | content-hash = "1e4fe841f5c19615414d95a92029bb80a192b1d659326cef63a641d0ffc15b06"
549 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "flask_blueprint_tutorial"
3 | version = "0.1.1"
4 | description = "Organize your Flask applications neatly via Blueprints."
5 | authors = ["Todd Birchard "]
6 | maintainers = ["Todd Birchard "]
7 | license = "MIT"
8 | readme = "README.md"
9 | homepage = "https://hackersandslackers.com/flask-blueprints/"
10 | repository = "https://github.com/hackersandslackers/flask-blueprint-tutorial/"
11 | documentation = "https://hackersandslackers.com/flask-blueprints/"
12 | keywords = ["Flask", "Flask-Blueprints", "Framework", "Python", "Tutorial"]
13 |
14 | [tool.poetry.dependencies]
15 | python = "^3.10,<4"
16 | flask = "*"
17 | flask_assets = "*"
18 | python-dotenv = "*"
19 | faker = "*"
20 | lesscpy = "*"
21 | cssmin = "*"
22 | jsmin = "*"
23 | black = "*"
24 | isort = "*"
25 | flake8 = "*"
26 | mypy = "*"
27 | gunicorn = "*"
28 |
29 | [tool.poetry.scripts]
30 | run = "wsgi:app"
31 |
32 | [tool.poetry.urls]
33 | issues = "https://github.com/hackersandslackers/flask-blueprint-tutorial/issues"
34 |
35 | [build-system]
36 | requires = ["poetry-core"]
37 | build-backend = "poetry.core.masonry.api"
38 |
39 | [tool.pylint.'MESSAGES CONTROL']
40 | disable = "C0103,C0301,W0703,W0621"
41 |
42 | [tool.black]
43 | line-length = 120
44 |
45 | [tool.isort]
46 | profile = "black"
47 | multi_line_output = 3
48 | src_paths = ["flask_blueprint_tutorial", "config", "wsgi"]
49 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "config:base"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | black==23.12.1 ; python_version >= "3.10" and python_version < "4.0"
2 | blinker==1.7.0 ; python_version >= "3.10" and python_version < "4.0"
3 | click==8.1.7 ; python_version >= "3.10" and python_version < "4.0"
4 | colorama==0.4.6 ; python_version >= "3.10" and python_version < "4.0" and platform_system == "Windows"
5 | cssmin==0.2.0 ; python_version >= "3.10" and python_version < "4.0"
6 | faker==21.0.0 ; python_version >= "3.10" and python_version < "4.0"
7 | flake8==6.1.0 ; python_version >= "3.10" and python_version < "4.0"
8 | flask-assets==2.1.0 ; python_version >= "3.10" and python_version < "4.0"
9 | flask==3.0.0 ; python_version >= "3.10" and python_version < "4.0"
10 | gunicorn==21.2.0 ; python_version >= "3.10" and python_version < "4.0"
11 | isort==5.13.2 ; python_version >= "3.10" and python_version < "4.0"
12 | itsdangerous==2.1.2 ; python_version >= "3.10" and python_version < "4.0"
13 | jinja2==3.1.2 ; python_version >= "3.10" and python_version < "4.0"
14 | jsmin==3.0.1 ; python_version >= "3.10" and python_version < "4.0"
15 | lesscpy==0.15.1 ; python_version >= "3.10" and python_version < "4.0"
16 | markupsafe==2.1.3 ; python_version >= "3.10" and python_version < "4.0"
17 | mccabe==0.7.0 ; python_version >= "3.10" and python_version < "4.0"
18 | mypy-extensions==1.0.0 ; python_version >= "3.10" and python_version < "4.0"
19 | mypy==1.8.0 ; python_version >= "3.10" and python_version < "4.0"
20 | packaging==23.2 ; python_version >= "3.10" and python_version < "4.0"
21 | pathspec==0.12.1 ; python_version >= "3.10" and python_version < "4.0"
22 | platformdirs==4.1.0 ; python_version >= "3.10" and python_version < "4.0"
23 | ply==3.11 ; python_version >= "3.10" and python_version < "4.0"
24 | pycodestyle==2.11.1 ; python_version >= "3.10" and python_version < "4.0"
25 | pyflakes==3.1.0 ; python_version >= "3.10" and python_version < "4.0"
26 | python-dateutil==2.8.2 ; python_version >= "3.10" and python_version < "4.0"
27 | python-dotenv==1.0.0 ; python_version >= "3.10" and python_version < "4.0"
28 | six==1.16.0 ; python_version >= "3.10" and python_version < "4.0"
29 | tomli==2.0.1 ; python_version >= "3.10" and python_version < "3.11"
30 | typing-extensions==4.9.0 ; python_version >= "3.10" and python_version < "4.0"
31 | webassets==2.0 ; python_version >= "3.10" and python_version < "4.0"
32 | werkzeug==3.0.1 ; python_version >= "3.10" and python_version < "4.0"
33 |
--------------------------------------------------------------------------------
/wsgi.py:
--------------------------------------------------------------------------------
1 | """Application entry point."""
2 | from flask_blueprint_tutorial import create_app
3 |
4 | app = create_app()
5 |
--------------------------------------------------------------------------------