├── .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 | ![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) 7 | ![GitHub Last Commit](https://img.shields.io/github/last-commit/google/skia.svg?style=flat-square&colorA=4c566a&colorB=a3be8c&logo=GitHub) 8 | [![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) 9 | [![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) 10 | [![GitHub Forks](https://img.shields.io/github/forks/hackersandslackers/flask-blueprint-tutorial.svg?style=flat-square&colorA=4c566a&logo=GitHub&colorB=ebcb8b)](https://github.com/hackersandslackers/flask-blueprint-tutorial/network) 11 | 12 | ![Flask Blueprint Tutorial](./.github/img/flaskblueprints@2x.jpg?raw=true) 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 |
21 | {% for product in products %} 22 | 23 | profile 24 |
{{ product.name }}
25 |
${{ product.salePrice }}
26 |
27 | {% endfor %} 28 |
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 | {{ product.name }} 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 | 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 | --------------------------------------------------------------------------------