diff --git a/docs/docs-upcoming/06_sql_storage/_category_.json b/docs/docs-upcoming/06_sql_storage/_category_.json
deleted file mode 100644
index 2d7b50b0..00000000
--- a/docs/docs-upcoming/06_sql_storage/_category_.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "label": "Store resources in a SQL database",
- "position": 6
-}
diff --git a/docs/docs-upcoming/06_sql_storage/01_project_overview/README.md b/docs/docs-upcoming/07_flask_admin/01_project_overview/README.md
similarity index 100%
rename from docs/docs-upcoming/06_sql_storage/01_project_overview/README.md
rename to docs/docs-upcoming/07_flask_admin/01_project_overview/README.md
diff --git a/docs/docs-upcoming/06_sql_storage/README.md b/docs/docs-upcoming/07_flask_admin/README.md
similarity index 100%
rename from docs/docs-upcoming/06_sql_storage/README.md
rename to docs/docs-upcoming/07_flask_admin/README.md
diff --git a/docs/docs-upcoming/08_flask_admin/_category_.json b/docs/docs-upcoming/07_flask_admin/_category_.json
similarity index 100%
rename from docs/docs-upcoming/08_flask_admin/_category_.json
rename to docs/docs-upcoming/07_flask_admin/_category_.json
diff --git a/docs/docs-upcoming/07_flask_sqlalchemy/_category_.json b/docs/docs-upcoming/07_flask_sqlalchemy/_category_.json
deleted file mode 100644
index 49500a13..00000000
--- a/docs/docs-upcoming/07_flask_sqlalchemy/_category_.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "label": "Simplifying storage with Flask-SQLAlchemy",
- "position": 7
-}
diff --git a/docs/docs-upcoming/07_flask_sqlalchemy/01_project_overview/README.md b/docs/docs-upcoming/08_flask_jwt_extended/01_project_overview/README.md
similarity index 100%
rename from docs/docs-upcoming/07_flask_sqlalchemy/01_project_overview/README.md
rename to docs/docs-upcoming/08_flask_jwt_extended/01_project_overview/README.md
diff --git a/docs/docs-upcoming/07_flask_sqlalchemy/README.md b/docs/docs-upcoming/08_flask_jwt_extended/README.md
similarity index 100%
rename from docs/docs-upcoming/07_flask_sqlalchemy/README.md
rename to docs/docs-upcoming/08_flask_jwt_extended/README.md
diff --git a/docs/docs-upcoming/09_flask_jwt_extended/_category_.json b/docs/docs-upcoming/08_flask_jwt_extended/_category_.json
similarity index 100%
rename from docs/docs-upcoming/09_flask_jwt_extended/_category_.json
rename to docs/docs-upcoming/08_flask_jwt_extended/_category_.json
diff --git a/docs/docs-upcoming/08_flask_admin/01_project_overview/README.md b/docs/docs-upcoming/09_git_crash_course/01_project_overview/README.md
similarity index 100%
rename from docs/docs-upcoming/08_flask_admin/01_project_overview/README.md
rename to docs/docs-upcoming/09_git_crash_course/01_project_overview/README.md
diff --git a/docs/docs-upcoming/08_flask_admin/README.md b/docs/docs-upcoming/09_git_crash_course/README.md
similarity index 100%
rename from docs/docs-upcoming/08_flask_admin/README.md
rename to docs/docs-upcoming/09_git_crash_course/README.md
diff --git a/docs/docs-upcoming/10_git_crash_course/_category_.json b/docs/docs-upcoming/09_git_crash_course/_category_.json
similarity index 100%
rename from docs/docs-upcoming/10_git_crash_course/_category_.json
rename to docs/docs-upcoming/09_git_crash_course/_category_.json
diff --git a/docs/docs-upcoming/09_flask_jwt_extended/01_project_overview/README.md b/docs/docs-upcoming/10_deploy_to_render/01_project_overview/README.md
similarity index 100%
rename from docs/docs-upcoming/09_flask_jwt_extended/01_project_overview/README.md
rename to docs/docs-upcoming/10_deploy_to_render/01_project_overview/README.md
diff --git a/docs/docs-upcoming/09_flask_jwt_extended/README.md b/docs/docs-upcoming/10_deploy_to_render/README.md
similarity index 100%
rename from docs/docs-upcoming/09_flask_jwt_extended/README.md
rename to docs/docs-upcoming/10_deploy_to_render/README.md
diff --git a/docs/docs-upcoming/11_deploy_to_render/_category_.json b/docs/docs-upcoming/10_deploy_to_render/_category_.json
similarity index 100%
rename from docs/docs-upcoming/11_deploy_to_render/_category_.json
rename to docs/docs-upcoming/10_deploy_to_render/_category_.json
diff --git a/docs/docs-upcoming/10_git_crash_course/01_project_overview/README.md b/docs/docs-upcoming/11_celery_background_tasks/01_project_overview/README.md
similarity index 100%
rename from docs/docs-upcoming/10_git_crash_course/01_project_overview/README.md
rename to docs/docs-upcoming/11_celery_background_tasks/01_project_overview/README.md
diff --git a/docs/docs-upcoming/10_git_crash_course/README.md b/docs/docs-upcoming/11_celery_background_tasks/README.md
similarity index 100%
rename from docs/docs-upcoming/10_git_crash_course/README.md
rename to docs/docs-upcoming/11_celery_background_tasks/README.md
diff --git a/docs/docs-upcoming/12_celery_background_tasks/_category_.json b/docs/docs-upcoming/11_celery_background_tasks/_category_.json
similarity index 100%
rename from docs/docs-upcoming/12_celery_background_tasks/_category_.json
rename to docs/docs-upcoming/11_celery_background_tasks/_category_.json
diff --git a/docs/docs-upcoming/11_deploy_to_render/01_project_overview/README.md b/docs/docs-upcoming/11_deploy_to_render/01_project_overview/README.md
deleted file mode 100644
index 17d79a19..00000000
--- a/docs/docs-upcoming/11_deploy_to_render/01_project_overview/README.md
+++ /dev/null
@@ -1 +0,0 @@
-# Project overview
\ No newline at end of file
diff --git a/docs/docs-upcoming/11_deploy_to_render/README.md b/docs/docs-upcoming/11_deploy_to_render/README.md
deleted file mode 100644
index 742f910c..00000000
--- a/docs/docs-upcoming/11_deploy_to_render/README.md
+++ /dev/null
@@ -1 +0,0 @@
-# Flask-Smorest for more efficient development
\ No newline at end of file
diff --git a/docs/docs-upcoming/12_celery_background_tasks/01_project_overview/README.md b/docs/docs-upcoming/12_celery_background_tasks/01_project_overview/README.md
deleted file mode 100644
index 17d79a19..00000000
--- a/docs/docs-upcoming/12_celery_background_tasks/01_project_overview/README.md
+++ /dev/null
@@ -1 +0,0 @@
-# Project overview
\ No newline at end of file
diff --git a/docs/docs-upcoming/12_celery_background_tasks/README.md b/docs/docs-upcoming/12_celery_background_tasks/README.md
deleted file mode 100644
index 742f910c..00000000
--- a/docs/docs-upcoming/12_celery_background_tasks/README.md
+++ /dev/null
@@ -1 +0,0 @@
-# Flask-Smorest for more efficient development
\ No newline at end of file
diff --git a/docs/docs/03_first_rest_api/README.md b/docs/docs/03_first_rest_api/README.md
deleted file mode 100644
index a3048a65..00000000
--- a/docs/docs/03_first_rest_api/README.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-name: Your first REST API
----
-
-# Your first REST API
-
-Description of the section goes here.
diff --git a/docs/docs/05_flask_smorest/03_api_with_method_views/README.md b/docs/docs/05_flask_smorest/03_api_with_method_views/README.md
index b4d808fa..cc1993da 100644
--- a/docs/docs/05_flask_smorest/03_api_with_method_views/README.md
+++ b/docs/docs/05_flask_smorest/03_api_with_method_views/README.md
@@ -202,7 +202,7 @@ app = Flask(__name__)
app.config["PROPAGATE_EXCEPTIONS"] = True
app.config["API_TITLE"] = "Stores REST API"
app.config["API_VERSION"] = "v1"
-app.config["OPENAPI_VERSION"] = "3.0.2"
+app.config["OPENAPI_VERSION"] = "3.0.3"
app.config["OPENAPI_URL_PREFIX"] = "/"
app.config["OPENAPI_SWAGGER_UI_PATH"] = "/swagger-ui"
app.config["OPENAPI_SWAGGER_UI_URL"] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist/"
diff --git a/docs/docs/05_flask_smorest/03_api_with_method_views/end/app.py b/docs/docs/05_flask_smorest/03_api_with_method_views/end/app.py
index 7057517e..5afd6e7b 100644
--- a/docs/docs/05_flask_smorest/03_api_with_method_views/end/app.py
+++ b/docs/docs/05_flask_smorest/03_api_with_method_views/end/app.py
@@ -10,7 +10,7 @@
app.config["PROPAGATE_EXCEPTIONS"] = True
app.config["API_TITLE"] = "Stores REST API"
app.config["API_VERSION"] = "v1"
-app.config["OPENAPI_VERSION"] = "3.0.2"
+app.config["OPENAPI_VERSION"] = "3.0.3"
app.config["OPENAPI_URL_PREFIX"] = "/"
app.config["OPENAPI_SWAGGER_UI_PATH"] = "/swagger-ui"
app.config["OPENAPI_SWAGGER_UI_URL"] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist/"
diff --git a/docs/docs/05_flask_smorest/04_marshmallow_schemas/end/app.py b/docs/docs/05_flask_smorest/04_marshmallow_schemas/end/app.py
index 7057517e..5afd6e7b 100644
--- a/docs/docs/05_flask_smorest/04_marshmallow_schemas/end/app.py
+++ b/docs/docs/05_flask_smorest/04_marshmallow_schemas/end/app.py
@@ -10,7 +10,7 @@
app.config["PROPAGATE_EXCEPTIONS"] = True
app.config["API_TITLE"] = "Stores REST API"
app.config["API_VERSION"] = "v1"
-app.config["OPENAPI_VERSION"] = "3.0.2"
+app.config["OPENAPI_VERSION"] = "3.0.3"
app.config["OPENAPI_URL_PREFIX"] = "/"
app.config["OPENAPI_SWAGGER_UI_PATH"] = "/swagger-ui"
app.config["OPENAPI_SWAGGER_UI_URL"] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist/"
diff --git a/docs/docs/05_flask_smorest/04_marshmallow_schemas/start/app.py b/docs/docs/05_flask_smorest/04_marshmallow_schemas/start/app.py
index 7057517e..5afd6e7b 100644
--- a/docs/docs/05_flask_smorest/04_marshmallow_schemas/start/app.py
+++ b/docs/docs/05_flask_smorest/04_marshmallow_schemas/start/app.py
@@ -10,7 +10,7 @@
app.config["PROPAGATE_EXCEPTIONS"] = True
app.config["API_TITLE"] = "Stores REST API"
app.config["API_VERSION"] = "v1"
-app.config["OPENAPI_VERSION"] = "3.0.2"
+app.config["OPENAPI_VERSION"] = "3.0.3"
app.config["OPENAPI_URL_PREFIX"] = "/"
app.config["OPENAPI_SWAGGER_UI_PATH"] = "/swagger-ui"
app.config["OPENAPI_SWAGGER_UI_URL"] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist/"
diff --git a/docs/docs/05_flask_smorest/05_validation_with_marshmallow/end/app.py b/docs/docs/05_flask_smorest/05_validation_with_marshmallow/end/app.py
index 7057517e..5afd6e7b 100644
--- a/docs/docs/05_flask_smorest/05_validation_with_marshmallow/end/app.py
+++ b/docs/docs/05_flask_smorest/05_validation_with_marshmallow/end/app.py
@@ -10,7 +10,7 @@
app.config["PROPAGATE_EXCEPTIONS"] = True
app.config["API_TITLE"] = "Stores REST API"
app.config["API_VERSION"] = "v1"
-app.config["OPENAPI_VERSION"] = "3.0.2"
+app.config["OPENAPI_VERSION"] = "3.0.3"
app.config["OPENAPI_URL_PREFIX"] = "/"
app.config["OPENAPI_SWAGGER_UI_PATH"] = "/swagger-ui"
app.config["OPENAPI_SWAGGER_UI_URL"] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist/"
diff --git a/docs/docs/05_flask_smorest/05_validation_with_marshmallow/start/app.py b/docs/docs/05_flask_smorest/05_validation_with_marshmallow/start/app.py
index 7057517e..5afd6e7b 100644
--- a/docs/docs/05_flask_smorest/05_validation_with_marshmallow/start/app.py
+++ b/docs/docs/05_flask_smorest/05_validation_with_marshmallow/start/app.py
@@ -10,7 +10,7 @@
app.config["PROPAGATE_EXCEPTIONS"] = True
app.config["API_TITLE"] = "Stores REST API"
app.config["API_VERSION"] = "v1"
-app.config["OPENAPI_VERSION"] = "3.0.2"
+app.config["OPENAPI_VERSION"] = "3.0.3"
app.config["OPENAPI_URL_PREFIX"] = "/"
app.config["OPENAPI_SWAGGER_UI_PATH"] = "/swagger-ui"
app.config["OPENAPI_SWAGGER_UI_URL"] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist/"
diff --git a/docs/docs/05_flask_smorest/06_decorating_responses/end/app.py b/docs/docs/05_flask_smorest/06_decorating_responses/end/app.py
index 7057517e..5afd6e7b 100644
--- a/docs/docs/05_flask_smorest/06_decorating_responses/end/app.py
+++ b/docs/docs/05_flask_smorest/06_decorating_responses/end/app.py
@@ -10,7 +10,7 @@
app.config["PROPAGATE_EXCEPTIONS"] = True
app.config["API_TITLE"] = "Stores REST API"
app.config["API_VERSION"] = "v1"
-app.config["OPENAPI_VERSION"] = "3.0.2"
+app.config["OPENAPI_VERSION"] = "3.0.3"
app.config["OPENAPI_URL_PREFIX"] = "/"
app.config["OPENAPI_SWAGGER_UI_PATH"] = "/swagger-ui"
app.config["OPENAPI_SWAGGER_UI_URL"] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist/"
diff --git a/docs/docs/05_flask_smorest/06_decorating_responses/start/app.py b/docs/docs/05_flask_smorest/06_decorating_responses/start/app.py
index 7057517e..5afd6e7b 100644
--- a/docs/docs/05_flask_smorest/06_decorating_responses/start/app.py
+++ b/docs/docs/05_flask_smorest/06_decorating_responses/start/app.py
@@ -10,7 +10,7 @@
app.config["PROPAGATE_EXCEPTIONS"] = True
app.config["API_TITLE"] = "Stores REST API"
app.config["API_VERSION"] = "v1"
-app.config["OPENAPI_VERSION"] = "3.0.2"
+app.config["OPENAPI_VERSION"] = "3.0.3"
app.config["OPENAPI_URL_PREFIX"] = "/"
app.config["OPENAPI_SWAGGER_UI_PATH"] = "/swagger-ui"
app.config["OPENAPI_SWAGGER_UI_URL"] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist/"
diff --git a/docs/docs/05_flask_smorest/README.md b/docs/docs/05_flask_smorest/README.md
deleted file mode 100644
index 742f910c..00000000
--- a/docs/docs/05_flask_smorest/README.md
+++ /dev/null
@@ -1 +0,0 @@
-# Flask-Smorest for more efficient development
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/01_project_overview_sqlalchemy/README.md b/docs/docs/06_sql_storage_sqlalchemy/01_project_overview_sqlalchemy/README.md
new file mode 100644
index 00000000..eff2e24f
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/01_project_overview_sqlalchemy/README.md
@@ -0,0 +1,50 @@
+---
+title: Project Overview, and why use SQLAlchemy
+description: Let's look at what we'll do in this section. There are no changes to the client-facing API at all, just changes internally to how we store data.
+---
+
+# Project Overview (and why use SQLAlchemy)
+
+- [x] Set metadata above
+- [ ] Start writing!
+
+In this section we'll make absolutely no changes to the API! However, we will completely change the way we store data.
+
+Up until now, we've been storing data in an "in-memory database": a couple of Python dictionaries. When we stop the app, the data is destroyed. This is obviously not great, so we want to move to a proper store that can keep data around between app restarts!
+
+We'll be using a relational database for data storage, and there are many different options: SQLite, MySQL, PostgreSQL, and others.
+
+At this point we have two options regarding how to interact with the database:
+
+1. We can write SQL code and execute it ourselves. For example, when we want to add an item to the database we'd write something like `INSERT INTO items (name, price, store_id) VALUES ("Chair", 17.99, 1)`.
+2. We can use an ORM, which can take Python objects and turn them into database rows.
+
+For this project, we are going to use an ORM because it makes the code much cleaner and simpler. Also, the ORM library (SQLAlchemy) helps us with many potential issues with using SQL, such as:
+
+- Multi-threading support
+- Handling creating the tables and defining the rows
+- Database migrations (with help of another library, Alembic)
+- Like mentioned, it makes the code cleaner, simpler, and shorter
+
+To get started, add the following to the `requirements.txt` file:
+
+```text title="requirements.txt"
+sqlalchemy
+flask-sqlalchemy
+```
+
+
+ What is Flask-SQLAlchemy?
+
+
SQLAlchemy is the ORM library, that helps map Python classes to database tables and columns, and turns Python objects of those classes into specific rows.
+
Flask-SQLAlchemy is a Flask extension which helps connect SQLAlchemy to Flask apps.
+
+
+
+With this, install your requirements (remember to activate your virtual environment first!).
+
+```
+pip install -r requirements.txt
+```
+
+Let's begin creating our SQLAlchemy models in the next lecture.
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/README.md b/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/README.md
new file mode 100644
index 00000000..3631c38a
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/README.md
@@ -0,0 +1,44 @@
+---
+title: Create a simple SQLAlchemy Model
+description: Lecture description goes here.
+---
+
+# Create a simple SQLAlchemy Model
+
+## Initialize the SQLAlchemy instance
+
+```python title="db.py"
+from flask_sqlalchemy import SQLAlchemy
+
+db = SQLAlchemy()
+```
+
+## Create models without relationships
+
+Every model inherits from `db.Model`. That way when we tell SQLAlchemy about them (in [Configure Flask-SQLAlchemy](../configure_flask_sqlalchemy))), it will know to look at them to create tables.
+
+Every model also has a few properties that let us interact with the database through the model, such as `query` (more on this in [Insert models in the database with SQLAlchemy](../insert_models_sqlalchemy)).
+
+```python title="models/item.py"
+from db import db
+
+
+class ItemModel(db.Model):
+ __tablename__ = "items"
+
+ id = db.Column(db.Integer, primary_key=True)
+ name = db.Column(db.String(80), unique=True, nullable=False)
+ price = db.Column(db.Float(precision=2), unique=False, nullable=False)
+ store_id = db.Column(db.Integer, unique=False, nullable=False)
+```
+
+```python title="models/store.py"
+from db import db
+
+
+class StoreModel(db.Model):
+ __tablename__ = "stores"
+
+ id = db.Column(db.Integer, primary_key=True)
+ name = db.Column(db.String(80), unique=True, nullable=False)
+```
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/end/.flaskenv b/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/end/.flaskenv
new file mode 100644
index 00000000..75473901
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/end/.flaskenv
@@ -0,0 +1,2 @@
+FLASK_APP=app
+FLASK_ENV=development
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/end/Dockerfile b/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/end/Dockerfile
new file mode 100644
index 00000000..652afba1
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/end/Dockerfile
@@ -0,0 +1,7 @@
+FROM python:3.10
+EXPOSE 5000
+WORKDIR /app
+COPY ./requirements.txt requirements.txt
+RUN pip install --no-cache-dir --upgrade -r requirements.txt
+COPY . .
+CMD ["flask", "run", "--host", "0.0.0.0"]
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/end/app.py b/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/end/app.py
new file mode 100644
index 00000000..5afd6e7b
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/end/app.py
@@ -0,0 +1,21 @@
+from flask import Flask
+from flask_smorest import Api
+
+from resources.item import blp as ItemBlueprint
+from resources.store import blp as StoreBlueprint
+
+
+app = Flask(__name__)
+
+app.config["PROPAGATE_EXCEPTIONS"] = True
+app.config["API_TITLE"] = "Stores REST API"
+app.config["API_VERSION"] = "v1"
+app.config["OPENAPI_VERSION"] = "3.0.3"
+app.config["OPENAPI_URL_PREFIX"] = "/"
+app.config["OPENAPI_SWAGGER_UI_PATH"] = "/swagger-ui"
+app.config["OPENAPI_SWAGGER_UI_URL"] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist/"
+
+api = Api(app)
+
+api.register_blueprint(ItemBlueprint)
+api.register_blueprint(StoreBlueprint)
diff --git a/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/end/db.py b/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/end/db.py
new file mode 100644
index 00000000..f0b13d6f
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/end/db.py
@@ -0,0 +1,3 @@
+from flask_sqlalchemy import SQLAlchemy
+
+db = SQLAlchemy()
diff --git a/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/end/models/__init__.py b/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/end/models/__init__.py
new file mode 100644
index 00000000..b57f3f8a
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/end/models/__init__.py
@@ -0,0 +1,5 @@
+from models.user import UserModel
+from models.item import ItemModel
+from models.tag import TagModel
+from models.store import StoreModel
+from models.item_tags import ItemsTags
diff --git a/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/end/models/item.py b/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/end/models/item.py
new file mode 100644
index 00000000..618fded0
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/end/models/item.py
@@ -0,0 +1,10 @@
+from db import db
+
+
+class ItemModel(db.Model):
+ __tablename__ = "items"
+
+ id = db.Column(db.Integer, primary_key=True)
+ name = db.Column(db.String(80), unique=True, nullable=False)
+ price = db.Column(db.Float(precision=2), unique=False, nullable=False)
+ store_id = db.Column(db.Integer, unique=False, nullable=False)
diff --git a/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/end/models/store.py b/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/end/models/store.py
new file mode 100644
index 00000000..49fc39dd
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/end/models/store.py
@@ -0,0 +1,8 @@
+from db import db
+
+
+class StoreModel(db.Model):
+ __tablename__ = "stores"
+
+ id = db.Column(db.Integer, primary_key=True)
+ name = db.Column(db.String(80), unique=True, nullable=False)
diff --git a/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/end/requirements.txt b/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/end/requirements.txt
new file mode 100644
index 00000000..4764bf34
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/end/requirements.txt
@@ -0,0 +1,4 @@
+flask
+flask-smorest
+python-dotenv
+marshmallow
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/end/resources/__init__.py b/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/end/resources/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/end/resources/item.py b/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/end/resources/item.py
new file mode 100644
index 00000000..b55192b2
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/end/resources/item.py
@@ -0,0 +1,36 @@
+from flask.views import MethodView
+from flask_smorest import Blueprint, abort
+from sqlalchemy.exc import SQLAlchemyError
+
+from db import db
+from models import ItemModel
+from schemas import ItemSchema, ItemUpdateSchema
+
+blp = Blueprint("Items", "items", description="Operations on items")
+
+
+@blp.route("/items/")
+class Item(MethodView):
+ @blp.response(200, ItemSchema)
+ def get(self, item_id):
+ raise NotImplementedError("Getting an item is not implemented.")
+
+ def delete(self, item_id):
+ raise NotImplementedError("Deleting an item is not implemented.")
+
+ @blp.arguments(ItemUpdateSchema)
+ @blp.response(200, ItemSchema)
+ def put(self, item_data, item_id):
+ raise NotImplementedError("Updating an item is not implemented.")
+
+
+@blp.route("/items")
+class ItemList(MethodView):
+ @blp.response(200, ItemSchema(many=True))
+ def get(self):
+ raise NotImplementedError("Listing items is not implemented.")
+
+ @blp.arguments(ItemSchema)
+ @blp.response(201, ItemSchema)
+ def post(self, item_data):
+ raise NotImplementedError("Creating an item is not implemented.")
diff --git a/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/end/resources/store.py b/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/end/resources/store.py
new file mode 100644
index 00000000..ce898fc5
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/end/resources/store.py
@@ -0,0 +1,32 @@
+from flask.views import MethodView
+from flask_smorest import Blueprint, abort
+from sqlalchemy.exc import SQLAlchemyError, IntegrityError
+
+from db import db
+from models import StoreModel
+from schemas import StoreSchema
+
+
+blp = Blueprint("Stores", "stores", description="Operations on stores")
+
+
+@blp.route("/stores/")
+class Store(MethodView):
+ @blp.response(200, StoreSchema)
+ def get(self, store_id):
+ raise NotImplementedError("Getting a store is not implemented.")
+
+ def delete(self, store_id):
+ raise NotImplementedError("Deleting a store is not implemented.")
+
+
+@blp.route("/stores")
+class StoreList(MethodView):
+ @blp.response(200, ItemSchema(many=True))
+ def get(self):
+ raise NotImplementedError("Listing stores is not implemented.")
+
+ @blp.arguments(StoreSchema)
+ @blp.response(201, StoreSchema)
+ def post(self, store_data):
+ raise NotImplementedError("Creating a store is not implemented.")
diff --git a/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/end/schemas.py b/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/end/schemas.py
new file mode 100644
index 00000000..0a4ff8d4
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/end/schemas.py
@@ -0,0 +1,18 @@
+from marshmallow import Schema, fields
+
+
+class ItemSchema(Schema):
+ id = fields.Str(dump_only=True)
+ name = fields.Str(required=True)
+ price = fields.Float(required=True)
+ store_id = fields.Int(required=True)
+
+
+class ItemUpdateSchema(Schema):
+ name = fields.Str()
+ price = fields.Float()
+
+
+class StoreSchema(Schema):
+ id = fields.Str(dump_only=True)
+ name = fields.Str(required=True)
diff --git a/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/start/.flaskenv b/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/start/.flaskenv
new file mode 100644
index 00000000..75473901
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/start/.flaskenv
@@ -0,0 +1,2 @@
+FLASK_APP=app
+FLASK_ENV=development
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/start/Dockerfile b/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/start/Dockerfile
new file mode 100644
index 00000000..652afba1
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/start/Dockerfile
@@ -0,0 +1,7 @@
+FROM python:3.10
+EXPOSE 5000
+WORKDIR /app
+COPY ./requirements.txt requirements.txt
+RUN pip install --no-cache-dir --upgrade -r requirements.txt
+COPY . .
+CMD ["flask", "run", "--host", "0.0.0.0"]
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/start/app.py b/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/start/app.py
new file mode 100644
index 00000000..5afd6e7b
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/start/app.py
@@ -0,0 +1,21 @@
+from flask import Flask
+from flask_smorest import Api
+
+from resources.item import blp as ItemBlueprint
+from resources.store import blp as StoreBlueprint
+
+
+app = Flask(__name__)
+
+app.config["PROPAGATE_EXCEPTIONS"] = True
+app.config["API_TITLE"] = "Stores REST API"
+app.config["API_VERSION"] = "v1"
+app.config["OPENAPI_VERSION"] = "3.0.3"
+app.config["OPENAPI_URL_PREFIX"] = "/"
+app.config["OPENAPI_SWAGGER_UI_PATH"] = "/swagger-ui"
+app.config["OPENAPI_SWAGGER_UI_URL"] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist/"
+
+api = Api(app)
+
+api.register_blueprint(ItemBlueprint)
+api.register_blueprint(StoreBlueprint)
diff --git a/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/start/db.py b/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/start/db.py
new file mode 100644
index 00000000..f0b13d6f
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/start/db.py
@@ -0,0 +1,3 @@
+from flask_sqlalchemy import SQLAlchemy
+
+db = SQLAlchemy()
diff --git a/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/start/requirements.txt b/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/start/requirements.txt
new file mode 100644
index 00000000..4764bf34
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/start/requirements.txt
@@ -0,0 +1,4 @@
+flask
+flask-smorest
+python-dotenv
+marshmallow
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/start/resources/__init__.py b/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/start/resources/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/start/resources/item.py b/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/start/resources/item.py
new file mode 100644
index 00000000..b55192b2
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/start/resources/item.py
@@ -0,0 +1,36 @@
+from flask.views import MethodView
+from flask_smorest import Blueprint, abort
+from sqlalchemy.exc import SQLAlchemyError
+
+from db import db
+from models import ItemModel
+from schemas import ItemSchema, ItemUpdateSchema
+
+blp = Blueprint("Items", "items", description="Operations on items")
+
+
+@blp.route("/items/")
+class Item(MethodView):
+ @blp.response(200, ItemSchema)
+ def get(self, item_id):
+ raise NotImplementedError("Getting an item is not implemented.")
+
+ def delete(self, item_id):
+ raise NotImplementedError("Deleting an item is not implemented.")
+
+ @blp.arguments(ItemUpdateSchema)
+ @blp.response(200, ItemSchema)
+ def put(self, item_data, item_id):
+ raise NotImplementedError("Updating an item is not implemented.")
+
+
+@blp.route("/items")
+class ItemList(MethodView):
+ @blp.response(200, ItemSchema(many=True))
+ def get(self):
+ raise NotImplementedError("Listing items is not implemented.")
+
+ @blp.arguments(ItemSchema)
+ @blp.response(201, ItemSchema)
+ def post(self, item_data):
+ raise NotImplementedError("Creating an item is not implemented.")
diff --git a/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/start/resources/store.py b/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/start/resources/store.py
new file mode 100644
index 00000000..ce898fc5
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/start/resources/store.py
@@ -0,0 +1,32 @@
+from flask.views import MethodView
+from flask_smorest import Blueprint, abort
+from sqlalchemy.exc import SQLAlchemyError, IntegrityError
+
+from db import db
+from models import StoreModel
+from schemas import StoreSchema
+
+
+blp = Blueprint("Stores", "stores", description="Operations on stores")
+
+
+@blp.route("/stores/")
+class Store(MethodView):
+ @blp.response(200, StoreSchema)
+ def get(self, store_id):
+ raise NotImplementedError("Getting a store is not implemented.")
+
+ def delete(self, store_id):
+ raise NotImplementedError("Deleting a store is not implemented.")
+
+
+@blp.route("/stores")
+class StoreList(MethodView):
+ @blp.response(200, ItemSchema(many=True))
+ def get(self):
+ raise NotImplementedError("Listing stores is not implemented.")
+
+ @blp.arguments(StoreSchema)
+ @blp.response(201, StoreSchema)
+ def post(self, store_data):
+ raise NotImplementedError("Creating a store is not implemented.")
diff --git a/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/start/schemas.py b/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/start/schemas.py
new file mode 100644
index 00000000..0a4ff8d4
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/02_create_simple_sqlalchemy_model/start/schemas.py
@@ -0,0 +1,18 @@
+from marshmallow import Schema, fields
+
+
+class ItemSchema(Schema):
+ id = fields.Str(dump_only=True)
+ name = fields.Str(required=True)
+ price = fields.Float(required=True)
+ store_id = fields.Int(required=True)
+
+
+class ItemUpdateSchema(Schema):
+ name = fields.Str()
+ price = fields.Float()
+
+
+class StoreSchema(Schema):
+ id = fields.Str(dump_only=True)
+ name = fields.Str(required=True)
diff --git a/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/README.md b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/README.md
new file mode 100644
index 00000000..6fb43a92
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/README.md
@@ -0,0 +1,120 @@
+---
+title: One-to-many relationships with SQLAlchemy
+description: Model relationships let us easily retrieve information about a related model, without having to do SQL JOINs manually.
+---
+
+# One-to-many relationships with SQLAlchemy
+
+- [x] Set metadata above
+- [x] Start writing!
+- [x] Create `start` folder
+- [x] Create `end` folder
+- [ ] Create per-file diff between `end` and `start` (use "Compare Folders")
+
+```python title="models/item.py"
+from db import db
+
+
+class ItemModel(db.Model):
+ __tablename__ = "items"
+
+ id = db.Column(db.Integer, primary_key=True)
+ name = db.Column(db.String(80), unique=True, nullable=False)
+ price = db.Column(db.Float(precision=2), unique=False, nullable=False)
+
+ # highlight-start
+ store_id = db.Column(
+ db.Integer, db.ForeignKey("stores.id"), unique=False, nullable=False
+ )
+ store = db.relationship("StoreModel", back_populates="items")
+ # highlight-end
+```
+
+```python title="models/store.py"
+from db import db
+
+
+class StoreModel(db.Model):
+ __tablename__ = "stores"
+
+ id = db.Column(db.Integer, primary_key=True)
+ name = db.Column(db.String(80), unique=True, nullable=False)
+
+ # highlight-start
+ items = db.relationship("ItemModel", back_populates="store", lazy="dynamic")
+ # highlight-end
+```
+
+To make it easier to import and use the models, I'll also create a `models/__init__.py` file that imports the models from their files:
+
+```python title="models/__init__.py"
+from models.store import StoreModel
+from models.item import ItemModel
+```
+
+## What is `lazy="dynamic"`?
+
+Without `lazy="dynamic"`, the `items` attribute of the `StoreModel` resolves to a list of `ItemModel` objects.
+
+With `lazy="dynamic"`, the `items` attribute resolves to a SQLAlchemy **query**, which has some benefits and drawbacks:
+
+- A key benefit is load speed. Because SQLAlchemy doesn't have to go to the `items` table and load items, stores will load faster.
+- A key drawback is accessing the `items` of a store isn't as easy.
+ - However this has another hidden benefit, which is that when you _do_ load items, you can do things like filtering before loading.
+
+Here's how you could get all the items, giving you a list of `ItemModel` objects. Assume `store` is a `StoreModel` instance:
+
+```python
+store.items.all()
+```
+
+And here's how you would do some filtering:
+
+```python
+store.items.filter_by(name=="Chair").first()
+```
+
+## Updating our marshmallow schemas
+
+Now that the models have these relationships, we can modify our marshmallow schemas so they will return some or all of the information about the related models.
+
+We do this with the `Nested` marshmallow field.
+
+:::caution
+Something to be careful about is having schema A which has a nested schema B, which has a nested schema A.
+
+This will lead to an infinite nesting, which is obviously never what you want!
+:::
+
+To avoid infinite nesting, we are renaming our schemas which _don't_ use nested fields to `Plain`, such as `PlainItemSchema` and `PlainStoreSchema`.
+
+Then the schemas that _do_ use nesting can be called `ItemSchema` and `StoreSchema`, and they inherit from the plain schemas. This reduces duplication and prevents infinite nesting.
+
+```python title="schemas.py"
+from marshmallow import Schema, fields
+
+
+class PlainItemSchema(Schema):
+ id = fields.Int(dump_only=True)
+ name = fields.Str(required=True)
+ price = fields.Float(required=True)
+
+
+class PlainStoreSchema(Schema):
+ id = fields.Int(dump_only=True)
+ name = fields.Str()
+
+
+class ItemSchema(PlainItemSchema):
+ store_id = fields.Int(required=True, load_only=True)
+ store = fields.Nested(lambda: PlainStoreSchema(), dump_only=True)
+
+
+class ItemUpdateSchema(Schema):
+ name = fields.Str()
+ price = fields.Float()
+
+
+class StoreSchema(PlainStoreSchema):
+ items = fields.List(fields.Nested(PlainItemSchema()), dump_only=True)
+```
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/end/.flaskenv b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/end/.flaskenv
new file mode 100644
index 00000000..75473901
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/end/.flaskenv
@@ -0,0 +1,2 @@
+FLASK_APP=app
+FLASK_ENV=development
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/end/Dockerfile b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/end/Dockerfile
new file mode 100644
index 00000000..652afba1
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/end/Dockerfile
@@ -0,0 +1,7 @@
+FROM python:3.10
+EXPOSE 5000
+WORKDIR /app
+COPY ./requirements.txt requirements.txt
+RUN pip install --no-cache-dir --upgrade -r requirements.txt
+COPY . .
+CMD ["flask", "run", "--host", "0.0.0.0"]
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/end/app.py b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/end/app.py
new file mode 100644
index 00000000..5afd6e7b
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/end/app.py
@@ -0,0 +1,21 @@
+from flask import Flask
+from flask_smorest import Api
+
+from resources.item import blp as ItemBlueprint
+from resources.store import blp as StoreBlueprint
+
+
+app = Flask(__name__)
+
+app.config["PROPAGATE_EXCEPTIONS"] = True
+app.config["API_TITLE"] = "Stores REST API"
+app.config["API_VERSION"] = "v1"
+app.config["OPENAPI_VERSION"] = "3.0.3"
+app.config["OPENAPI_URL_PREFIX"] = "/"
+app.config["OPENAPI_SWAGGER_UI_PATH"] = "/swagger-ui"
+app.config["OPENAPI_SWAGGER_UI_URL"] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist/"
+
+api = Api(app)
+
+api.register_blueprint(ItemBlueprint)
+api.register_blueprint(StoreBlueprint)
diff --git a/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/end/db.py b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/end/db.py
new file mode 100644
index 00000000..f0b13d6f
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/end/db.py
@@ -0,0 +1,3 @@
+from flask_sqlalchemy import SQLAlchemy
+
+db = SQLAlchemy()
diff --git a/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/end/models/__init__.py b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/end/models/__init__.py
new file mode 100644
index 00000000..b57f3f8a
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/end/models/__init__.py
@@ -0,0 +1,5 @@
+from models.user import UserModel
+from models.item import ItemModel
+from models.tag import TagModel
+from models.store import StoreModel
+from models.item_tags import ItemsTags
diff --git a/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/end/models/item.py b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/end/models/item.py
new file mode 100644
index 00000000..74797e0e
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/end/models/item.py
@@ -0,0 +1,14 @@
+from db import db
+
+
+class ItemModel(db.Model):
+ __tablename__ = "items"
+
+ id = db.Column(db.Integer, primary_key=True)
+ name = db.Column(db.String(80), unique=True, nullable=False)
+ price = db.Column(db.Float(precision=2), unique=False, nullable=False)
+
+ store_id = db.Column(
+ db.Integer, db.ForeignKey("stores.id"), unique=False, nullable=False
+ )
+ store = db.relationship("StoreModel", back_populates="items")
diff --git a/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/end/models/store.py b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/end/models/store.py
new file mode 100644
index 00000000..699147f9
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/end/models/store.py
@@ -0,0 +1,10 @@
+from db import db
+
+
+class StoreModel(db.Model):
+ __tablename__ = "stores"
+
+ id = db.Column(db.Integer, primary_key=True)
+ name = db.Column(db.String(80), unique=True, nullable=False)
+
+ items = db.relationship("ItemModel", back_populates="store", lazy="dynamic")
diff --git a/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/end/requirements.txt b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/end/requirements.txt
new file mode 100644
index 00000000..4764bf34
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/end/requirements.txt
@@ -0,0 +1,4 @@
+flask
+flask-smorest
+python-dotenv
+marshmallow
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/end/resources/__init__.py b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/end/resources/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/end/resources/item.py b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/end/resources/item.py
new file mode 100644
index 00000000..b55192b2
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/end/resources/item.py
@@ -0,0 +1,36 @@
+from flask.views import MethodView
+from flask_smorest import Blueprint, abort
+from sqlalchemy.exc import SQLAlchemyError
+
+from db import db
+from models import ItemModel
+from schemas import ItemSchema, ItemUpdateSchema
+
+blp = Blueprint("Items", "items", description="Operations on items")
+
+
+@blp.route("/items/")
+class Item(MethodView):
+ @blp.response(200, ItemSchema)
+ def get(self, item_id):
+ raise NotImplementedError("Getting an item is not implemented.")
+
+ def delete(self, item_id):
+ raise NotImplementedError("Deleting an item is not implemented.")
+
+ @blp.arguments(ItemUpdateSchema)
+ @blp.response(200, ItemSchema)
+ def put(self, item_data, item_id):
+ raise NotImplementedError("Updating an item is not implemented.")
+
+
+@blp.route("/items")
+class ItemList(MethodView):
+ @blp.response(200, ItemSchema(many=True))
+ def get(self):
+ raise NotImplementedError("Listing items is not implemented.")
+
+ @blp.arguments(ItemSchema)
+ @blp.response(201, ItemSchema)
+ def post(self, item_data):
+ raise NotImplementedError("Creating an item is not implemented.")
diff --git a/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/end/resources/store.py b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/end/resources/store.py
new file mode 100644
index 00000000..ce898fc5
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/end/resources/store.py
@@ -0,0 +1,32 @@
+from flask.views import MethodView
+from flask_smorest import Blueprint, abort
+from sqlalchemy.exc import SQLAlchemyError, IntegrityError
+
+from db import db
+from models import StoreModel
+from schemas import StoreSchema
+
+
+blp = Blueprint("Stores", "stores", description="Operations on stores")
+
+
+@blp.route("/stores/")
+class Store(MethodView):
+ @blp.response(200, StoreSchema)
+ def get(self, store_id):
+ raise NotImplementedError("Getting a store is not implemented.")
+
+ def delete(self, store_id):
+ raise NotImplementedError("Deleting a store is not implemented.")
+
+
+@blp.route("/stores")
+class StoreList(MethodView):
+ @blp.response(200, ItemSchema(many=True))
+ def get(self):
+ raise NotImplementedError("Listing stores is not implemented.")
+
+ @blp.arguments(StoreSchema)
+ @blp.response(201, StoreSchema)
+ def post(self, store_data):
+ raise NotImplementedError("Creating a store is not implemented.")
diff --git a/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/end/schemas.py b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/end/schemas.py
new file mode 100644
index 00000000..2619c4ee
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/end/schemas.py
@@ -0,0 +1,26 @@
+from marshmallow import Schema, fields
+
+
+class PlainItemSchema(Schema):
+ id = fields.Int(dump_only=True)
+ name = fields.Str(required=True)
+ price = fields.Float(required=True)
+
+
+class PlainStoreSchema(Schema):
+ id = fields.Int(dump_only=True)
+ name = fields.Str()
+
+
+class ItemSchema(PlainItemSchema):
+ store_id = fields.Int(required=True, load_only=True)
+ store = fields.Nested(lambda: PlainStoreSchema(), dump_only=True)
+
+
+class ItemUpdateSchema(Schema):
+ name = fields.Str()
+ price = fields.Float()
+
+
+class StoreSchema(PlainStoreSchema):
+ items = fields.List(fields.Nested(PlainItemSchema()), dump_only=True)
diff --git a/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/start/.flaskenv b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/start/.flaskenv
new file mode 100644
index 00000000..75473901
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/start/.flaskenv
@@ -0,0 +1,2 @@
+FLASK_APP=app
+FLASK_ENV=development
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/start/Dockerfile b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/start/Dockerfile
new file mode 100644
index 00000000..652afba1
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/start/Dockerfile
@@ -0,0 +1,7 @@
+FROM python:3.10
+EXPOSE 5000
+WORKDIR /app
+COPY ./requirements.txt requirements.txt
+RUN pip install --no-cache-dir --upgrade -r requirements.txt
+COPY . .
+CMD ["flask", "run", "--host", "0.0.0.0"]
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/start/app.py b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/start/app.py
new file mode 100644
index 00000000..5afd6e7b
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/start/app.py
@@ -0,0 +1,21 @@
+from flask import Flask
+from flask_smorest import Api
+
+from resources.item import blp as ItemBlueprint
+from resources.store import blp as StoreBlueprint
+
+
+app = Flask(__name__)
+
+app.config["PROPAGATE_EXCEPTIONS"] = True
+app.config["API_TITLE"] = "Stores REST API"
+app.config["API_VERSION"] = "v1"
+app.config["OPENAPI_VERSION"] = "3.0.3"
+app.config["OPENAPI_URL_PREFIX"] = "/"
+app.config["OPENAPI_SWAGGER_UI_PATH"] = "/swagger-ui"
+app.config["OPENAPI_SWAGGER_UI_URL"] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist/"
+
+api = Api(app)
+
+api.register_blueprint(ItemBlueprint)
+api.register_blueprint(StoreBlueprint)
diff --git a/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/start/db.py b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/start/db.py
new file mode 100644
index 00000000..f0b13d6f
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/start/db.py
@@ -0,0 +1,3 @@
+from flask_sqlalchemy import SQLAlchemy
+
+db = SQLAlchemy()
diff --git a/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/start/models/__init__.py b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/start/models/__init__.py
new file mode 100644
index 00000000..b57f3f8a
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/start/models/__init__.py
@@ -0,0 +1,5 @@
+from models.user import UserModel
+from models.item import ItemModel
+from models.tag import TagModel
+from models.store import StoreModel
+from models.item_tags import ItemsTags
diff --git a/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/start/models/item.py b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/start/models/item.py
new file mode 100644
index 00000000..618fded0
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/start/models/item.py
@@ -0,0 +1,10 @@
+from db import db
+
+
+class ItemModel(db.Model):
+ __tablename__ = "items"
+
+ id = db.Column(db.Integer, primary_key=True)
+ name = db.Column(db.String(80), unique=True, nullable=False)
+ price = db.Column(db.Float(precision=2), unique=False, nullable=False)
+ store_id = db.Column(db.Integer, unique=False, nullable=False)
diff --git a/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/start/models/store.py b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/start/models/store.py
new file mode 100644
index 00000000..49fc39dd
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/start/models/store.py
@@ -0,0 +1,8 @@
+from db import db
+
+
+class StoreModel(db.Model):
+ __tablename__ = "stores"
+
+ id = db.Column(db.Integer, primary_key=True)
+ name = db.Column(db.String(80), unique=True, nullable=False)
diff --git a/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/start/requirements.txt b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/start/requirements.txt
new file mode 100644
index 00000000..4764bf34
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/start/requirements.txt
@@ -0,0 +1,4 @@
+flask
+flask-smorest
+python-dotenv
+marshmallow
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/start/resources/__init__.py b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/start/resources/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/start/resources/item.py b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/start/resources/item.py
new file mode 100644
index 00000000..b55192b2
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/start/resources/item.py
@@ -0,0 +1,36 @@
+from flask.views import MethodView
+from flask_smorest import Blueprint, abort
+from sqlalchemy.exc import SQLAlchemyError
+
+from db import db
+from models import ItemModel
+from schemas import ItemSchema, ItemUpdateSchema
+
+blp = Blueprint("Items", "items", description="Operations on items")
+
+
+@blp.route("/items/")
+class Item(MethodView):
+ @blp.response(200, ItemSchema)
+ def get(self, item_id):
+ raise NotImplementedError("Getting an item is not implemented.")
+
+ def delete(self, item_id):
+ raise NotImplementedError("Deleting an item is not implemented.")
+
+ @blp.arguments(ItemUpdateSchema)
+ @blp.response(200, ItemSchema)
+ def put(self, item_data, item_id):
+ raise NotImplementedError("Updating an item is not implemented.")
+
+
+@blp.route("/items")
+class ItemList(MethodView):
+ @blp.response(200, ItemSchema(many=True))
+ def get(self):
+ raise NotImplementedError("Listing items is not implemented.")
+
+ @blp.arguments(ItemSchema)
+ @blp.response(201, ItemSchema)
+ def post(self, item_data):
+ raise NotImplementedError("Creating an item is not implemented.")
diff --git a/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/start/resources/store.py b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/start/resources/store.py
new file mode 100644
index 00000000..ce898fc5
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/start/resources/store.py
@@ -0,0 +1,32 @@
+from flask.views import MethodView
+from flask_smorest import Blueprint, abort
+from sqlalchemy.exc import SQLAlchemyError, IntegrityError
+
+from db import db
+from models import StoreModel
+from schemas import StoreSchema
+
+
+blp = Blueprint("Stores", "stores", description="Operations on stores")
+
+
+@blp.route("/stores/")
+class Store(MethodView):
+ @blp.response(200, StoreSchema)
+ def get(self, store_id):
+ raise NotImplementedError("Getting a store is not implemented.")
+
+ def delete(self, store_id):
+ raise NotImplementedError("Deleting a store is not implemented.")
+
+
+@blp.route("/stores")
+class StoreList(MethodView):
+ @blp.response(200, ItemSchema(many=True))
+ def get(self):
+ raise NotImplementedError("Listing stores is not implemented.")
+
+ @blp.arguments(StoreSchema)
+ @blp.response(201, StoreSchema)
+ def post(self, store_data):
+ raise NotImplementedError("Creating a store is not implemented.")
diff --git a/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/start/schemas.py b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/start/schemas.py
new file mode 100644
index 00000000..2619c4ee
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/03_one_to_many_relationships_sqlalchemy/start/schemas.py
@@ -0,0 +1,26 @@
+from marshmallow import Schema, fields
+
+
+class PlainItemSchema(Schema):
+ id = fields.Int(dump_only=True)
+ name = fields.Str(required=True)
+ price = fields.Float(required=True)
+
+
+class PlainStoreSchema(Schema):
+ id = fields.Int(dump_only=True)
+ name = fields.Str()
+
+
+class ItemSchema(PlainItemSchema):
+ store_id = fields.Int(required=True, load_only=True)
+ store = fields.Nested(lambda: PlainStoreSchema(), dump_only=True)
+
+
+class ItemUpdateSchema(Schema):
+ name = fields.Str()
+ price = fields.Float()
+
+
+class StoreSchema(PlainStoreSchema):
+ items = fields.List(fields.Nested(PlainItemSchema()), dump_only=True)
diff --git a/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/README.md b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/README.md
new file mode 100644
index 00000000..c01db467
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/README.md
@@ -0,0 +1,117 @@
+---
+title: Configure Flask-SQLAlchemy
+description: Link Flask-SQLAlchemy with our Flask app and create the initial tables.
+---
+
+# Configure Flask-SQLAlchemy
+
+- [x] Set metadata above
+- [x] Start writing!
+- [x] Create `start` folder
+- [x] Create `end` folder
+- [ ] Create per-file diff between `end` and `start` (use "Compare Folders")
+
+We want to add two imports to `app.py`:
+
+```python title="app.py"
+from db import db
+
+import models
+```
+
+## The Flask app factory pattern
+
+Up until now, we've been creating the `app` variable (which is the Flask app) directly in `app.py`.
+
+With the app factory pattern, we write a function that _returns_ `app`. That way we can _pass configuration values_ to the function, so that we configure the app before getting it back.
+
+This is especially useful for testing, but also if you want to do things like have staging and production apps.
+
+To do the app factory, all we do is place all the app-creation code inside a function which **must be called `create_app()`**.
+
+```python title="app.py"
+from flask import Flask
+from flask_smorest import Api
+
+from db import db
+
+import models
+
+from resources.item import blp as ItemBlueprint
+from resources.store import blp as StoreBlueprint
+
+# highlight-start
+def create_app():
+ app = Flask(__name__)
+ app.config["PROPAGATE_EXCEPTIONS"] = True
+ app.config["API_TITLE"] = "Stores REST API"
+ app.config["API_VERSION"] = "v1"
+ app.config["OPENAPI_VERSION"] = "3.0.3"
+ app.config["OPENAPI_URL_PREFIX"] = "/"
+ app.config["OPENAPI_SWAGGER_UI_PATH"] = "/swagger-ui"
+ app.config[
+ "OPENAPI_SWAGGER_UI_URL"
+ ] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist/"
+ api = Api(app)
+
+ api.register_blueprint(ItemBlueprint)
+ api.register_blueprint(StoreBlueprint)
+
+ return app
+# highlight-end
+```
+
+## Add Flask-SQLAlchemy code to the app factory
+
+```python title="app.py"
+from flask import Flask
+from flask_smorest import Api
+
+from db import db
+
+import models
+
+from resources.item import blp as ItemBlueprint
+from resources.store import blp as StoreBlueprint
+
+# highlight-start
+def create_app(db_url=None):
+ # highlight-end
+ app = Flask(__name__)
+ app.config["PROPAGATE_EXCEPTIONS"] = True
+ app.config["API_TITLE"] = "Stores REST API"
+ app.config["API_VERSION"] = "v1"
+ app.config["OPENAPI_VERSION"] = "3.0.3"
+ app.config["OPENAPI_URL_PREFIX"] = "/"
+ app.config["OPENAPI_SWAGGER_UI_PATH"] = "/swagger-ui"
+ app.config[
+ "OPENAPI_SWAGGER_UI_URL"
+ ] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist/"
+ # highlight-start
+ app.config["SQLALCHEMY_DATABASE_URI"] = db_url or os.getenv("DATABASE_URL", "sqlite:///data.db")
+ app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
+ db.init_app(app)
+ # highlight-end
+ api = Api(app)
+
+ # highlight-start
+ @app.before_first_request
+ def create_tables():
+ db.create_all()
+ # highlight-end
+
+ api.register_blueprint(ItemBlueprint)
+ api.register_blueprint(StoreBlueprint)
+
+ return app
+```
+
+We've done three things:
+
+1. Added the `db_url` parameter. This lets us create an app with a certain database URL, or alternatively try to fetch the database URL from the environment variables. The default value will be a local SQLite file, if we don't pass a value ourselves and it isn't in the environment.
+2. Added two SQLAlchemy values to `app.config`. One is the database URL (or URI), the other is a [configuration option](https://flask-sqlalchemy.palletsprojects.com/en/2.x/config/) which improves performance.
+3. Registered a function to run before our Flask app handles its first request. The function will tell SQLAlchemy to use what it knows in order to create all the database tables we need.
+
+:::tip How does SQLAlchemy know what tables to create?
+The line `import models` lets SQLAlchemy know what models exist in our application. Because they are `db.Model` instances, SQLAlchemy will look at their `__tablename__` and defined `db.Column` attributes to create the tables.
+:::
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/end/.flaskenv b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/end/.flaskenv
new file mode 100644
index 00000000..75473901
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/end/.flaskenv
@@ -0,0 +1,2 @@
+FLASK_APP=app
+FLASK_ENV=development
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/end/Dockerfile b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/end/Dockerfile
new file mode 100644
index 00000000..652afba1
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/end/Dockerfile
@@ -0,0 +1,7 @@
+FROM python:3.10
+EXPOSE 5000
+WORKDIR /app
+COPY ./requirements.txt requirements.txt
+RUN pip install --no-cache-dir --upgrade -r requirements.txt
+COPY . .
+CMD ["flask", "run", "--host", "0.0.0.0"]
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/end/app.py b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/end/app.py
new file mode 100644
index 00000000..f3f475ca
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/end/app.py
@@ -0,0 +1,35 @@
+from flask import Flask
+from flask_smorest import Api
+
+from db import db
+
+import models
+
+from resources.item import blp as ItemBlueprint
+from resources.store import blp as StoreBlueprint
+
+
+def create_app(db_url=None):
+ app = Flask(__name__)
+ app.config["API_TITLE"] = "Stores REST API"
+ app.config["API_VERSION"] = "v1"
+ app.config["OPENAPI_VERSION"] = "3.0.3"
+ app.config["OPENAPI_URL_PREFIX"] = "/"
+ app.config["OPENAPI_SWAGGER_UI_PATH"] = "/swagger-ui"
+ app.config[
+ "OPENAPI_SWAGGER_UI_URL"
+ ] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist/"
+ app.config["SQLALCHEMY_DATABASE_URI"] = db_url or "sqlite:///data.db"
+ app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
+ app.config["PROPAGATE_EXCEPTIONS"] = True
+ db.init_app(app)
+ api = Api(app)
+
+ @app.before_first_request
+ def create_tables():
+ db.create_all()
+
+ api.register_blueprint(ItemBlueprint)
+ api.register_blueprint(StoreBlueprint)
+
+ return app
diff --git a/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/end/db.py b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/end/db.py
new file mode 100644
index 00000000..f0b13d6f
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/end/db.py
@@ -0,0 +1,3 @@
+from flask_sqlalchemy import SQLAlchemy
+
+db = SQLAlchemy()
diff --git a/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/end/models/__init__.py b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/end/models/__init__.py
new file mode 100644
index 00000000..b57f3f8a
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/end/models/__init__.py
@@ -0,0 +1,5 @@
+from models.user import UserModel
+from models.item import ItemModel
+from models.tag import TagModel
+from models.store import StoreModel
+from models.item_tags import ItemsTags
diff --git a/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/end/models/item.py b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/end/models/item.py
new file mode 100644
index 00000000..74797e0e
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/end/models/item.py
@@ -0,0 +1,14 @@
+from db import db
+
+
+class ItemModel(db.Model):
+ __tablename__ = "items"
+
+ id = db.Column(db.Integer, primary_key=True)
+ name = db.Column(db.String(80), unique=True, nullable=False)
+ price = db.Column(db.Float(precision=2), unique=False, nullable=False)
+
+ store_id = db.Column(
+ db.Integer, db.ForeignKey("stores.id"), unique=False, nullable=False
+ )
+ store = db.relationship("StoreModel", back_populates="items")
diff --git a/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/end/models/store.py b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/end/models/store.py
new file mode 100644
index 00000000..699147f9
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/end/models/store.py
@@ -0,0 +1,10 @@
+from db import db
+
+
+class StoreModel(db.Model):
+ __tablename__ = "stores"
+
+ id = db.Column(db.Integer, primary_key=True)
+ name = db.Column(db.String(80), unique=True, nullable=False)
+
+ items = db.relationship("ItemModel", back_populates="store", lazy="dynamic")
diff --git a/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/end/requirements.txt b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/end/requirements.txt
new file mode 100644
index 00000000..4764bf34
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/end/requirements.txt
@@ -0,0 +1,4 @@
+flask
+flask-smorest
+python-dotenv
+marshmallow
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/end/resources/__init__.py b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/end/resources/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/end/resources/item.py b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/end/resources/item.py
new file mode 100644
index 00000000..b55192b2
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/end/resources/item.py
@@ -0,0 +1,36 @@
+from flask.views import MethodView
+from flask_smorest import Blueprint, abort
+from sqlalchemy.exc import SQLAlchemyError
+
+from db import db
+from models import ItemModel
+from schemas import ItemSchema, ItemUpdateSchema
+
+blp = Blueprint("Items", "items", description="Operations on items")
+
+
+@blp.route("/items/")
+class Item(MethodView):
+ @blp.response(200, ItemSchema)
+ def get(self, item_id):
+ raise NotImplementedError("Getting an item is not implemented.")
+
+ def delete(self, item_id):
+ raise NotImplementedError("Deleting an item is not implemented.")
+
+ @blp.arguments(ItemUpdateSchema)
+ @blp.response(200, ItemSchema)
+ def put(self, item_data, item_id):
+ raise NotImplementedError("Updating an item is not implemented.")
+
+
+@blp.route("/items")
+class ItemList(MethodView):
+ @blp.response(200, ItemSchema(many=True))
+ def get(self):
+ raise NotImplementedError("Listing items is not implemented.")
+
+ @blp.arguments(ItemSchema)
+ @blp.response(201, ItemSchema)
+ def post(self, item_data):
+ raise NotImplementedError("Creating an item is not implemented.")
diff --git a/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/end/resources/store.py b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/end/resources/store.py
new file mode 100644
index 00000000..ce898fc5
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/end/resources/store.py
@@ -0,0 +1,32 @@
+from flask.views import MethodView
+from flask_smorest import Blueprint, abort
+from sqlalchemy.exc import SQLAlchemyError, IntegrityError
+
+from db import db
+from models import StoreModel
+from schemas import StoreSchema
+
+
+blp = Blueprint("Stores", "stores", description="Operations on stores")
+
+
+@blp.route("/stores/")
+class Store(MethodView):
+ @blp.response(200, StoreSchema)
+ def get(self, store_id):
+ raise NotImplementedError("Getting a store is not implemented.")
+
+ def delete(self, store_id):
+ raise NotImplementedError("Deleting a store is not implemented.")
+
+
+@blp.route("/stores")
+class StoreList(MethodView):
+ @blp.response(200, ItemSchema(many=True))
+ def get(self):
+ raise NotImplementedError("Listing stores is not implemented.")
+
+ @blp.arguments(StoreSchema)
+ @blp.response(201, StoreSchema)
+ def post(self, store_data):
+ raise NotImplementedError("Creating a store is not implemented.")
diff --git a/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/end/schemas.py b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/end/schemas.py
new file mode 100644
index 00000000..2619c4ee
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/end/schemas.py
@@ -0,0 +1,26 @@
+from marshmallow import Schema, fields
+
+
+class PlainItemSchema(Schema):
+ id = fields.Int(dump_only=True)
+ name = fields.Str(required=True)
+ price = fields.Float(required=True)
+
+
+class PlainStoreSchema(Schema):
+ id = fields.Int(dump_only=True)
+ name = fields.Str()
+
+
+class ItemSchema(PlainItemSchema):
+ store_id = fields.Int(required=True, load_only=True)
+ store = fields.Nested(lambda: PlainStoreSchema(), dump_only=True)
+
+
+class ItemUpdateSchema(Schema):
+ name = fields.Str()
+ price = fields.Float()
+
+
+class StoreSchema(PlainStoreSchema):
+ items = fields.List(fields.Nested(PlainItemSchema()), dump_only=True)
diff --git a/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/start/.flaskenv b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/start/.flaskenv
new file mode 100644
index 00000000..75473901
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/start/.flaskenv
@@ -0,0 +1,2 @@
+FLASK_APP=app
+FLASK_ENV=development
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/start/Dockerfile b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/start/Dockerfile
new file mode 100644
index 00000000..652afba1
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/start/Dockerfile
@@ -0,0 +1,7 @@
+FROM python:3.10
+EXPOSE 5000
+WORKDIR /app
+COPY ./requirements.txt requirements.txt
+RUN pip install --no-cache-dir --upgrade -r requirements.txt
+COPY . .
+CMD ["flask", "run", "--host", "0.0.0.0"]
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/start/app.py b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/start/app.py
new file mode 100644
index 00000000..5afd6e7b
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/start/app.py
@@ -0,0 +1,21 @@
+from flask import Flask
+from flask_smorest import Api
+
+from resources.item import blp as ItemBlueprint
+from resources.store import blp as StoreBlueprint
+
+
+app = Flask(__name__)
+
+app.config["PROPAGATE_EXCEPTIONS"] = True
+app.config["API_TITLE"] = "Stores REST API"
+app.config["API_VERSION"] = "v1"
+app.config["OPENAPI_VERSION"] = "3.0.3"
+app.config["OPENAPI_URL_PREFIX"] = "/"
+app.config["OPENAPI_SWAGGER_UI_PATH"] = "/swagger-ui"
+app.config["OPENAPI_SWAGGER_UI_URL"] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist/"
+
+api = Api(app)
+
+api.register_blueprint(ItemBlueprint)
+api.register_blueprint(StoreBlueprint)
diff --git a/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/start/db.py b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/start/db.py
new file mode 100644
index 00000000..f0b13d6f
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/start/db.py
@@ -0,0 +1,3 @@
+from flask_sqlalchemy import SQLAlchemy
+
+db = SQLAlchemy()
diff --git a/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/start/models/__init__.py b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/start/models/__init__.py
new file mode 100644
index 00000000..b57f3f8a
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/start/models/__init__.py
@@ -0,0 +1,5 @@
+from models.user import UserModel
+from models.item import ItemModel
+from models.tag import TagModel
+from models.store import StoreModel
+from models.item_tags import ItemsTags
diff --git a/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/start/models/item.py b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/start/models/item.py
new file mode 100644
index 00000000..74797e0e
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/start/models/item.py
@@ -0,0 +1,14 @@
+from db import db
+
+
+class ItemModel(db.Model):
+ __tablename__ = "items"
+
+ id = db.Column(db.Integer, primary_key=True)
+ name = db.Column(db.String(80), unique=True, nullable=False)
+ price = db.Column(db.Float(precision=2), unique=False, nullable=False)
+
+ store_id = db.Column(
+ db.Integer, db.ForeignKey("stores.id"), unique=False, nullable=False
+ )
+ store = db.relationship("StoreModel", back_populates="items")
diff --git a/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/start/models/store.py b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/start/models/store.py
new file mode 100644
index 00000000..699147f9
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/start/models/store.py
@@ -0,0 +1,10 @@
+from db import db
+
+
+class StoreModel(db.Model):
+ __tablename__ = "stores"
+
+ id = db.Column(db.Integer, primary_key=True)
+ name = db.Column(db.String(80), unique=True, nullable=False)
+
+ items = db.relationship("ItemModel", back_populates="store", lazy="dynamic")
diff --git a/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/start/requirements.txt b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/start/requirements.txt
new file mode 100644
index 00000000..4764bf34
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/start/requirements.txt
@@ -0,0 +1,4 @@
+flask
+flask-smorest
+python-dotenv
+marshmallow
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/start/resources/__init__.py b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/start/resources/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/start/resources/item.py b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/start/resources/item.py
new file mode 100644
index 00000000..b55192b2
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/start/resources/item.py
@@ -0,0 +1,36 @@
+from flask.views import MethodView
+from flask_smorest import Blueprint, abort
+from sqlalchemy.exc import SQLAlchemyError
+
+from db import db
+from models import ItemModel
+from schemas import ItemSchema, ItemUpdateSchema
+
+blp = Blueprint("Items", "items", description="Operations on items")
+
+
+@blp.route("/items/")
+class Item(MethodView):
+ @blp.response(200, ItemSchema)
+ def get(self, item_id):
+ raise NotImplementedError("Getting an item is not implemented.")
+
+ def delete(self, item_id):
+ raise NotImplementedError("Deleting an item is not implemented.")
+
+ @blp.arguments(ItemUpdateSchema)
+ @blp.response(200, ItemSchema)
+ def put(self, item_data, item_id):
+ raise NotImplementedError("Updating an item is not implemented.")
+
+
+@blp.route("/items")
+class ItemList(MethodView):
+ @blp.response(200, ItemSchema(many=True))
+ def get(self):
+ raise NotImplementedError("Listing items is not implemented.")
+
+ @blp.arguments(ItemSchema)
+ @blp.response(201, ItemSchema)
+ def post(self, item_data):
+ raise NotImplementedError("Creating an item is not implemented.")
diff --git a/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/start/resources/store.py b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/start/resources/store.py
new file mode 100644
index 00000000..ce898fc5
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/start/resources/store.py
@@ -0,0 +1,32 @@
+from flask.views import MethodView
+from flask_smorest import Blueprint, abort
+from sqlalchemy.exc import SQLAlchemyError, IntegrityError
+
+from db import db
+from models import StoreModel
+from schemas import StoreSchema
+
+
+blp = Blueprint("Stores", "stores", description="Operations on stores")
+
+
+@blp.route("/stores/")
+class Store(MethodView):
+ @blp.response(200, StoreSchema)
+ def get(self, store_id):
+ raise NotImplementedError("Getting a store is not implemented.")
+
+ def delete(self, store_id):
+ raise NotImplementedError("Deleting a store is not implemented.")
+
+
+@blp.route("/stores")
+class StoreList(MethodView):
+ @blp.response(200, ItemSchema(many=True))
+ def get(self):
+ raise NotImplementedError("Listing stores is not implemented.")
+
+ @blp.arguments(StoreSchema)
+ @blp.response(201, StoreSchema)
+ def post(self, store_data):
+ raise NotImplementedError("Creating a store is not implemented.")
diff --git a/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/start/schemas.py b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/start/schemas.py
new file mode 100644
index 00000000..2619c4ee
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/04_configure_flask_sqlalchemy/start/schemas.py
@@ -0,0 +1,26 @@
+from marshmallow import Schema, fields
+
+
+class PlainItemSchema(Schema):
+ id = fields.Int(dump_only=True)
+ name = fields.Str(required=True)
+ price = fields.Float(required=True)
+
+
+class PlainStoreSchema(Schema):
+ id = fields.Int(dump_only=True)
+ name = fields.Str()
+
+
+class ItemSchema(PlainItemSchema):
+ store_id = fields.Int(required=True, load_only=True)
+ store = fields.Nested(lambda: PlainStoreSchema(), dump_only=True)
+
+
+class ItemUpdateSchema(Schema):
+ name = fields.Str()
+ price = fields.Float()
+
+
+class StoreSchema(PlainStoreSchema):
+ items = fields.List(fields.Nested(PlainItemSchema()), dump_only=True)
diff --git a/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/README.md b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/README.md
new file mode 100644
index 00000000..83636b9d
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/README.md
@@ -0,0 +1,56 @@
+---
+title: Insert models in the database with SQLAlchemy
+description: Learn how to use SQLAlchemy to add new rows to our SQL database.
+---
+
+# Insert models in the database with SQLAlchemy
+
+- [x] Set metadata above
+- [x] Start writing!
+- [x] Create `start` folder
+- [x] Create `end` folder
+- [ ] Create per-file diff between `end` and `start` (use "Compare Folders")
+
+Inserting models with SQLAlchemy couldn't be easier! We'll use the `db.session`[^1] variable to `.add()` a model. Let's begin working on our `Item` resource:
+
+```python title="resources/item.py"
+@blp.arguments(ItemSchema)
+@blp.response(201, ItemSchema)
+def post(self, item_data):
+ item = ItemModel(**item_data)
+
+ try:
+ db.session.add(item)
+ db.session.commit()
+ except SQLAlchemyError:
+ abort(500, message="An error occurred while inserting the item.")
+
+ return item
+```
+
+Similarly in our `Store` resource:
+
+```python title="resources/store.py"
+@blp.arguments(StoreSchema)
+ @blp.response(201, StoreSchema)
+ def post(self, store_data):
+ store = StoreModel(**store_data)
+ try:
+ db.session.add(store)
+ db.session.commit()
+ except IntegrityError:
+ abort(
+ 400,
+ message="A store with that name already exists.",
+ )
+ except SQLAlchemyError:
+ abort(500, message="An error occurred creating the store.")
+
+ return store
+```
+
+Note here we're catching two different errors, `IntegrityError` for when a client attempts to create a store with a name that already exists, and `SQLAlchemyError` for anything else.
+
+Since the `StoreModel`'s `name` column is marked as `unique=True`, then an `IntegrityError` is raised when we try to insert another row with the same name.
+
+[^1]: [Session Basics (SQLAlchemy Documentation)](https://docs.sqlalchemy.org/en/14/orm/session_basics.html)
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/end/.flaskenv b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/end/.flaskenv
new file mode 100644
index 00000000..75473901
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/end/.flaskenv
@@ -0,0 +1,2 @@
+FLASK_APP=app
+FLASK_ENV=development
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/end/Dockerfile b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/end/Dockerfile
new file mode 100644
index 00000000..652afba1
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/end/Dockerfile
@@ -0,0 +1,7 @@
+FROM python:3.10
+EXPOSE 5000
+WORKDIR /app
+COPY ./requirements.txt requirements.txt
+RUN pip install --no-cache-dir --upgrade -r requirements.txt
+COPY . .
+CMD ["flask", "run", "--host", "0.0.0.0"]
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/end/app.py b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/end/app.py
new file mode 100644
index 00000000..f3f475ca
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/end/app.py
@@ -0,0 +1,35 @@
+from flask import Flask
+from flask_smorest import Api
+
+from db import db
+
+import models
+
+from resources.item import blp as ItemBlueprint
+from resources.store import blp as StoreBlueprint
+
+
+def create_app(db_url=None):
+ app = Flask(__name__)
+ app.config["API_TITLE"] = "Stores REST API"
+ app.config["API_VERSION"] = "v1"
+ app.config["OPENAPI_VERSION"] = "3.0.3"
+ app.config["OPENAPI_URL_PREFIX"] = "/"
+ app.config["OPENAPI_SWAGGER_UI_PATH"] = "/swagger-ui"
+ app.config[
+ "OPENAPI_SWAGGER_UI_URL"
+ ] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist/"
+ app.config["SQLALCHEMY_DATABASE_URI"] = db_url or "sqlite:///data.db"
+ app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
+ app.config["PROPAGATE_EXCEPTIONS"] = True
+ db.init_app(app)
+ api = Api(app)
+
+ @app.before_first_request
+ def create_tables():
+ db.create_all()
+
+ api.register_blueprint(ItemBlueprint)
+ api.register_blueprint(StoreBlueprint)
+
+ return app
diff --git a/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/end/db.py b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/end/db.py
new file mode 100644
index 00000000..f0b13d6f
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/end/db.py
@@ -0,0 +1,3 @@
+from flask_sqlalchemy import SQLAlchemy
+
+db = SQLAlchemy()
diff --git a/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/end/models/__init__.py b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/end/models/__init__.py
new file mode 100644
index 00000000..b57f3f8a
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/end/models/__init__.py
@@ -0,0 +1,5 @@
+from models.user import UserModel
+from models.item import ItemModel
+from models.tag import TagModel
+from models.store import StoreModel
+from models.item_tags import ItemsTags
diff --git a/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/end/models/item.py b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/end/models/item.py
new file mode 100644
index 00000000..74797e0e
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/end/models/item.py
@@ -0,0 +1,14 @@
+from db import db
+
+
+class ItemModel(db.Model):
+ __tablename__ = "items"
+
+ id = db.Column(db.Integer, primary_key=True)
+ name = db.Column(db.String(80), unique=True, nullable=False)
+ price = db.Column(db.Float(precision=2), unique=False, nullable=False)
+
+ store_id = db.Column(
+ db.Integer, db.ForeignKey("stores.id"), unique=False, nullable=False
+ )
+ store = db.relationship("StoreModel", back_populates="items")
diff --git a/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/end/models/store.py b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/end/models/store.py
new file mode 100644
index 00000000..699147f9
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/end/models/store.py
@@ -0,0 +1,10 @@
+from db import db
+
+
+class StoreModel(db.Model):
+ __tablename__ = "stores"
+
+ id = db.Column(db.Integer, primary_key=True)
+ name = db.Column(db.String(80), unique=True, nullable=False)
+
+ items = db.relationship("ItemModel", back_populates="store", lazy="dynamic")
diff --git a/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/end/requirements.txt b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/end/requirements.txt
new file mode 100644
index 00000000..4764bf34
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/end/requirements.txt
@@ -0,0 +1,4 @@
+flask
+flask-smorest
+python-dotenv
+marshmallow
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/end/resources/__init__.py b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/end/resources/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/end/resources/item.py b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/end/resources/item.py
new file mode 100644
index 00000000..92e51bd6
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/end/resources/item.py
@@ -0,0 +1,44 @@
+from flask.views import MethodView
+from flask_smorest import Blueprint, abort
+from sqlalchemy.exc import SQLAlchemyError
+
+from db import db
+from models import ItemModel
+from schemas import ItemSchema, ItemUpdateSchema
+
+blp = Blueprint("Items", "items", description="Operations on items")
+
+
+@blp.route("/items/")
+class Item(MethodView):
+ @blp.response(200, ItemSchema)
+ def get(self, item_id):
+ raise NotImplementedError("Getting an item is not implemented.")
+
+ def delete(self, item_id):
+ raise NotImplementedError("Deleting an item is not implemented.")
+
+ @blp.arguments(ItemUpdateSchema)
+ @blp.response(200, ItemSchema)
+ def put(self, item_data, item_id):
+ raise NotImplementedError("Updating an item is not implemented.")
+
+
+@blp.route("/items")
+class ItemList(MethodView):
+ @blp.response(200, ItemSchema(many=True))
+ def get(self):
+ raise NotImplementedError("Listing items is not implemented.")
+
+ @blp.arguments(ItemSchema)
+ @blp.response(201, ItemSchema)
+ def post(self, item_data):
+ item = ItemModel(**item_data)
+
+ try:
+ db.session.add(item)
+ db.session.commit()
+ except SQLAlchemyError:
+ abort(500, message="An error occurred while inserting the item.")
+
+ return item
diff --git a/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/end/resources/store.py b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/end/resources/store.py
new file mode 100644
index 00000000..c04e6a05
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/end/resources/store.py
@@ -0,0 +1,44 @@
+from flask.views import MethodView
+from flask_smorest import Blueprint, abort
+from sqlalchemy.exc import SQLAlchemyError, IntegrityError
+
+from db import db
+from models import StoreModel
+from schemas import StoreSchema
+
+
+blp = Blueprint("Stores", "stores", description="Operations on stores")
+
+
+@blp.route("/stores/")
+class Store(MethodView):
+ @blp.response(200, StoreSchema)
+ def get(self, store_id):
+ raise NotImplementedError("Getting a store is not implemented.")
+
+ def delete(self, store_id):
+ raise NotImplementedError("Deleting a store is not implemented.")
+
+
+@blp.route("/stores")
+class StoreList(MethodView):
+ @blp.response(200, ItemSchema(many=True))
+ def get(self):
+ raise NotImplementedError("Listing stores is not implemented.")
+
+ @blp.arguments(StoreSchema)
+ @blp.response(201, StoreSchema)
+ def post(self, store_data):
+ store = StoreModel(**store_data)
+ try:
+ db.session.add(store)
+ db.session.commit()
+ except IntegrityError:
+ abort(
+ 400,
+ message="A store with that name already exists.",
+ )
+ except SQLAlchemyError:
+ abort(500, message="An error occurred creating the store.")
+
+ return store
diff --git a/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/end/schemas.py b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/end/schemas.py
new file mode 100644
index 00000000..2619c4ee
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/end/schemas.py
@@ -0,0 +1,26 @@
+from marshmallow import Schema, fields
+
+
+class PlainItemSchema(Schema):
+ id = fields.Int(dump_only=True)
+ name = fields.Str(required=True)
+ price = fields.Float(required=True)
+
+
+class PlainStoreSchema(Schema):
+ id = fields.Int(dump_only=True)
+ name = fields.Str()
+
+
+class ItemSchema(PlainItemSchema):
+ store_id = fields.Int(required=True, load_only=True)
+ store = fields.Nested(lambda: PlainStoreSchema(), dump_only=True)
+
+
+class ItemUpdateSchema(Schema):
+ name = fields.Str()
+ price = fields.Float()
+
+
+class StoreSchema(PlainStoreSchema):
+ items = fields.List(fields.Nested(PlainItemSchema()), dump_only=True)
diff --git a/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/start/.flaskenv b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/start/.flaskenv
new file mode 100644
index 00000000..75473901
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/start/.flaskenv
@@ -0,0 +1,2 @@
+FLASK_APP=app
+FLASK_ENV=development
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/start/Dockerfile b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/start/Dockerfile
new file mode 100644
index 00000000..652afba1
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/start/Dockerfile
@@ -0,0 +1,7 @@
+FROM python:3.10
+EXPOSE 5000
+WORKDIR /app
+COPY ./requirements.txt requirements.txt
+RUN pip install --no-cache-dir --upgrade -r requirements.txt
+COPY . .
+CMD ["flask", "run", "--host", "0.0.0.0"]
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/start/app.py b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/start/app.py
new file mode 100644
index 00000000..f3f475ca
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/start/app.py
@@ -0,0 +1,35 @@
+from flask import Flask
+from flask_smorest import Api
+
+from db import db
+
+import models
+
+from resources.item import blp as ItemBlueprint
+from resources.store import blp as StoreBlueprint
+
+
+def create_app(db_url=None):
+ app = Flask(__name__)
+ app.config["API_TITLE"] = "Stores REST API"
+ app.config["API_VERSION"] = "v1"
+ app.config["OPENAPI_VERSION"] = "3.0.3"
+ app.config["OPENAPI_URL_PREFIX"] = "/"
+ app.config["OPENAPI_SWAGGER_UI_PATH"] = "/swagger-ui"
+ app.config[
+ "OPENAPI_SWAGGER_UI_URL"
+ ] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist/"
+ app.config["SQLALCHEMY_DATABASE_URI"] = db_url or "sqlite:///data.db"
+ app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
+ app.config["PROPAGATE_EXCEPTIONS"] = True
+ db.init_app(app)
+ api = Api(app)
+
+ @app.before_first_request
+ def create_tables():
+ db.create_all()
+
+ api.register_blueprint(ItemBlueprint)
+ api.register_blueprint(StoreBlueprint)
+
+ return app
diff --git a/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/start/db.py b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/start/db.py
new file mode 100644
index 00000000..f0b13d6f
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/start/db.py
@@ -0,0 +1,3 @@
+from flask_sqlalchemy import SQLAlchemy
+
+db = SQLAlchemy()
diff --git a/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/start/models/__init__.py b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/start/models/__init__.py
new file mode 100644
index 00000000..b57f3f8a
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/start/models/__init__.py
@@ -0,0 +1,5 @@
+from models.user import UserModel
+from models.item import ItemModel
+from models.tag import TagModel
+from models.store import StoreModel
+from models.item_tags import ItemsTags
diff --git a/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/start/models/item.py b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/start/models/item.py
new file mode 100644
index 00000000..74797e0e
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/start/models/item.py
@@ -0,0 +1,14 @@
+from db import db
+
+
+class ItemModel(db.Model):
+ __tablename__ = "items"
+
+ id = db.Column(db.Integer, primary_key=True)
+ name = db.Column(db.String(80), unique=True, nullable=False)
+ price = db.Column(db.Float(precision=2), unique=False, nullable=False)
+
+ store_id = db.Column(
+ db.Integer, db.ForeignKey("stores.id"), unique=False, nullable=False
+ )
+ store = db.relationship("StoreModel", back_populates="items")
diff --git a/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/start/models/store.py b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/start/models/store.py
new file mode 100644
index 00000000..699147f9
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/start/models/store.py
@@ -0,0 +1,10 @@
+from db import db
+
+
+class StoreModel(db.Model):
+ __tablename__ = "stores"
+
+ id = db.Column(db.Integer, primary_key=True)
+ name = db.Column(db.String(80), unique=True, nullable=False)
+
+ items = db.relationship("ItemModel", back_populates="store", lazy="dynamic")
diff --git a/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/start/requirements.txt b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/start/requirements.txt
new file mode 100644
index 00000000..4764bf34
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/start/requirements.txt
@@ -0,0 +1,4 @@
+flask
+flask-smorest
+python-dotenv
+marshmallow
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/start/resources/__init__.py b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/start/resources/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/start/resources/item.py b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/start/resources/item.py
new file mode 100644
index 00000000..b55192b2
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/start/resources/item.py
@@ -0,0 +1,36 @@
+from flask.views import MethodView
+from flask_smorest import Blueprint, abort
+from sqlalchemy.exc import SQLAlchemyError
+
+from db import db
+from models import ItemModel
+from schemas import ItemSchema, ItemUpdateSchema
+
+blp = Blueprint("Items", "items", description="Operations on items")
+
+
+@blp.route("/items/")
+class Item(MethodView):
+ @blp.response(200, ItemSchema)
+ def get(self, item_id):
+ raise NotImplementedError("Getting an item is not implemented.")
+
+ def delete(self, item_id):
+ raise NotImplementedError("Deleting an item is not implemented.")
+
+ @blp.arguments(ItemUpdateSchema)
+ @blp.response(200, ItemSchema)
+ def put(self, item_data, item_id):
+ raise NotImplementedError("Updating an item is not implemented.")
+
+
+@blp.route("/items")
+class ItemList(MethodView):
+ @blp.response(200, ItemSchema(many=True))
+ def get(self):
+ raise NotImplementedError("Listing items is not implemented.")
+
+ @blp.arguments(ItemSchema)
+ @blp.response(201, ItemSchema)
+ def post(self, item_data):
+ raise NotImplementedError("Creating an item is not implemented.")
diff --git a/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/start/resources/store.py b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/start/resources/store.py
new file mode 100644
index 00000000..ce898fc5
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/start/resources/store.py
@@ -0,0 +1,32 @@
+from flask.views import MethodView
+from flask_smorest import Blueprint, abort
+from sqlalchemy.exc import SQLAlchemyError, IntegrityError
+
+from db import db
+from models import StoreModel
+from schemas import StoreSchema
+
+
+blp = Blueprint("Stores", "stores", description="Operations on stores")
+
+
+@blp.route("/stores/")
+class Store(MethodView):
+ @blp.response(200, StoreSchema)
+ def get(self, store_id):
+ raise NotImplementedError("Getting a store is not implemented.")
+
+ def delete(self, store_id):
+ raise NotImplementedError("Deleting a store is not implemented.")
+
+
+@blp.route("/stores")
+class StoreList(MethodView):
+ @blp.response(200, ItemSchema(many=True))
+ def get(self):
+ raise NotImplementedError("Listing stores is not implemented.")
+
+ @blp.arguments(StoreSchema)
+ @blp.response(201, StoreSchema)
+ def post(self, store_data):
+ raise NotImplementedError("Creating a store is not implemented.")
diff --git a/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/start/schemas.py b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/start/schemas.py
new file mode 100644
index 00000000..2619c4ee
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/05_insert_models_sqlalchemy/start/schemas.py
@@ -0,0 +1,26 @@
+from marshmallow import Schema, fields
+
+
+class PlainItemSchema(Schema):
+ id = fields.Int(dump_only=True)
+ name = fields.Str(required=True)
+ price = fields.Float(required=True)
+
+
+class PlainStoreSchema(Schema):
+ id = fields.Int(dump_only=True)
+ name = fields.Str()
+
+
+class ItemSchema(PlainItemSchema):
+ store_id = fields.Int(required=True, load_only=True)
+ store = fields.Nested(lambda: PlainStoreSchema(), dump_only=True)
+
+
+class ItemUpdateSchema(Schema):
+ name = fields.Str()
+ price = fields.Float()
+
+
+class StoreSchema(PlainStoreSchema):
+ items = fields.List(fields.Nested(PlainItemSchema()), dump_only=True)
diff --git a/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/README.md b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/README.md
new file mode 100644
index 00000000..d77e0e6b
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/README.md
@@ -0,0 +1,77 @@
+---
+title: Get models by ID from the database
+description: Learn how to fetch a specific model using its primary key column, and how to return a 404 page if it isn't found.
+---
+
+# Get models by ID from the database using SQLAlchemy
+
+- [x] Set metadata above
+- [x] Start writing!
+- [x] Create `start` folder
+- [x] Create `end` folder
+- [ ] Create per-file diff between `end` and `start` (use "Compare Folders")
+
+Using the model class's `query` attribute, we have access to two very handy methods:
+
+- `ItemModel.query.get(item_id)` gives us an `ItemModel` object from the database where the `item_id` matches the primary key.
+- `ItemModel.query.get_or_404(item_id)` does the same, but makes Flask immediately return a "Not Found" message, together with a 404 error code, if no model can be found with that ID in the database.
+
+:::tip
+When we use `.get_or_404()` and nothing is found, this is the response from the API:
+
+```json
+{"code": 404, "status": "Not Found"}
+```
+
+The status code of this response is also 404.
+:::
+
+We're going to use `.get_or_404()` repeatedly in our resources!
+
+For now, and since we'll need an `ItemModel` instance in all our `Item` resource methods, let's add that:
+
+```python title="resources/item.py"
+@blp.route("/items/")
+class Item(MethodView):
+ @blp.response(200, ItemSchema)
+ def get(self, item_id):
+ # highlight-start
+ item = ItemModel.query.get_or_404(item_id)
+ return item
+ # highlight-end
+
+ def delete(self, item_id):
+ # highlight-start
+ item = ItemModel.query.get_or_404(item_id)
+ # highlight-end
+ raise NotImplementedError("Deleting an item is not implemented.")
+
+ @blp.arguments(ItemUpdateSchema)
+ @blp.response(200, ItemSchema)
+ def put(self, item_data, item_id):
+ # highlight-start
+ item = ItemModel.query.get_or_404(item_id)
+ # highlight-end
+ raise NotImplementedError("Updating an item is not implemented.")
+```
+
+Similarly in our `Store` resource:
+
+```python title="resources/store.py"
+@blp.route("/stores/")
+class Store(MethodView):
+ @blp.response(200, StoreSchema)
+ def get(self, store_id):
+ # highlight-start
+ store = StoreModel.query.get_or_404(store_id)
+ return store
+ # highlight-end
+
+ def delete(self, store_id):
+ # highlight-start
+ store = StoreModel.query.get_or_404(store_id)
+ # highlight-end
+ raise NotImplementedError("Deleting a store is not implemented.")
+```
+
+With this, we're ready to continue!
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/end/.flaskenv b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/end/.flaskenv
new file mode 100644
index 00000000..75473901
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/end/.flaskenv
@@ -0,0 +1,2 @@
+FLASK_APP=app
+FLASK_ENV=development
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/end/Dockerfile b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/end/Dockerfile
new file mode 100644
index 00000000..652afba1
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/end/Dockerfile
@@ -0,0 +1,7 @@
+FROM python:3.10
+EXPOSE 5000
+WORKDIR /app
+COPY ./requirements.txt requirements.txt
+RUN pip install --no-cache-dir --upgrade -r requirements.txt
+COPY . .
+CMD ["flask", "run", "--host", "0.0.0.0"]
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/end/app.py b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/end/app.py
new file mode 100644
index 00000000..f3f475ca
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/end/app.py
@@ -0,0 +1,35 @@
+from flask import Flask
+from flask_smorest import Api
+
+from db import db
+
+import models
+
+from resources.item import blp as ItemBlueprint
+from resources.store import blp as StoreBlueprint
+
+
+def create_app(db_url=None):
+ app = Flask(__name__)
+ app.config["API_TITLE"] = "Stores REST API"
+ app.config["API_VERSION"] = "v1"
+ app.config["OPENAPI_VERSION"] = "3.0.3"
+ app.config["OPENAPI_URL_PREFIX"] = "/"
+ app.config["OPENAPI_SWAGGER_UI_PATH"] = "/swagger-ui"
+ app.config[
+ "OPENAPI_SWAGGER_UI_URL"
+ ] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist/"
+ app.config["SQLALCHEMY_DATABASE_URI"] = db_url or "sqlite:///data.db"
+ app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
+ app.config["PROPAGATE_EXCEPTIONS"] = True
+ db.init_app(app)
+ api = Api(app)
+
+ @app.before_first_request
+ def create_tables():
+ db.create_all()
+
+ api.register_blueprint(ItemBlueprint)
+ api.register_blueprint(StoreBlueprint)
+
+ return app
diff --git a/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/end/db.py b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/end/db.py
new file mode 100644
index 00000000..f0b13d6f
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/end/db.py
@@ -0,0 +1,3 @@
+from flask_sqlalchemy import SQLAlchemy
+
+db = SQLAlchemy()
diff --git a/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/end/models/__init__.py b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/end/models/__init__.py
new file mode 100644
index 00000000..b57f3f8a
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/end/models/__init__.py
@@ -0,0 +1,5 @@
+from models.user import UserModel
+from models.item import ItemModel
+from models.tag import TagModel
+from models.store import StoreModel
+from models.item_tags import ItemsTags
diff --git a/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/end/models/item.py b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/end/models/item.py
new file mode 100644
index 00000000..74797e0e
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/end/models/item.py
@@ -0,0 +1,14 @@
+from db import db
+
+
+class ItemModel(db.Model):
+ __tablename__ = "items"
+
+ id = db.Column(db.Integer, primary_key=True)
+ name = db.Column(db.String(80), unique=True, nullable=False)
+ price = db.Column(db.Float(precision=2), unique=False, nullable=False)
+
+ store_id = db.Column(
+ db.Integer, db.ForeignKey("stores.id"), unique=False, nullable=False
+ )
+ store = db.relationship("StoreModel", back_populates="items")
diff --git a/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/end/models/store.py b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/end/models/store.py
new file mode 100644
index 00000000..699147f9
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/end/models/store.py
@@ -0,0 +1,10 @@
+from db import db
+
+
+class StoreModel(db.Model):
+ __tablename__ = "stores"
+
+ id = db.Column(db.Integer, primary_key=True)
+ name = db.Column(db.String(80), unique=True, nullable=False)
+
+ items = db.relationship("ItemModel", back_populates="store", lazy="dynamic")
diff --git a/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/end/requirements.txt b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/end/requirements.txt
new file mode 100644
index 00000000..4764bf34
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/end/requirements.txt
@@ -0,0 +1,4 @@
+flask
+flask-smorest
+python-dotenv
+marshmallow
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/end/resources/__init__.py b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/end/resources/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/end/resources/item.py b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/end/resources/item.py
new file mode 100644
index 00000000..26226af2
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/end/resources/item.py
@@ -0,0 +1,48 @@
+from flask.views import MethodView
+from flask_smorest import Blueprint, abort
+from sqlalchemy.exc import SQLAlchemyError
+
+from db import db
+from models import ItemModel
+from schemas import ItemSchema, ItemUpdateSchema
+
+blp = Blueprint("Items", "items", description="Operations on items")
+
+
+@blp.route("/items/")
+class Item(MethodView):
+ @blp.response(200, ItemSchema)
+ def get(self, item_id):
+ item = ItemModel.query.get_or_404(item_id)
+ return item
+
+ def delete(self, item_id):
+ item = ItemModel.query.get_or_404(item_id)
+ raise NotImplementedError("Deleting an item is not implemented.")
+
+ @blp.arguments(ItemUpdateSchema)
+ @blp.response(200, ItemSchema)
+ def put(self, item_data, item_id):
+ item = ItemModel.query.get_or_404(item_id)
+
+ raise NotImplementedError("Updating an item is not implemented.")
+
+
+@blp.route("/items")
+class ItemList(MethodView):
+ @blp.response(200, ItemSchema(many=True))
+ def get(self):
+ raise NotImplementedError("Listing items is not implemented.")
+
+ @blp.arguments(ItemSchema)
+ @blp.response(201, ItemSchema)
+ def post(self, item_data):
+ item = ItemModel(**item_data)
+
+ try:
+ db.session.add(item)
+ db.session.commit()
+ except SQLAlchemyError:
+ abort(500, message="An error occurred while inserting the item.")
+
+ return item
diff --git a/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/end/resources/store.py b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/end/resources/store.py
new file mode 100644
index 00000000..a750ae10
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/end/resources/store.py
@@ -0,0 +1,46 @@
+from flask.views import MethodView
+from flask_smorest import Blueprint, abort
+from sqlalchemy.exc import SQLAlchemyError, IntegrityError
+
+from db import db
+from models import StoreModel
+from schemas import StoreSchema
+
+
+blp = Blueprint("Stores", "stores", description="Operations on stores")
+
+
+@blp.route("/stores/")
+class Store(MethodView):
+ @blp.response(200, StoreSchema)
+ def get(self, store_id):
+ store = StoreModel.query.get_or_404(store_id)
+ return store
+
+ def delete(self, store_id):
+ store = StoreModel.query.get_or_404(store_id)
+ raise NotImplementedError("Deleting a store is not implemented.")
+
+
+@blp.route("/stores")
+class StoreList(MethodView):
+ @blp.response(200, ItemSchema(many=True))
+ def get(self):
+ raise NotImplementedError("Listing stores is not implemented.")
+
+ @blp.arguments(StoreSchema)
+ @blp.response(201, StoreSchema)
+ def post(self, store_data):
+ store = StoreModel(**store_data)
+ try:
+ db.session.add(store)
+ db.session.commit()
+ except IntegrityError:
+ abort(
+ 400,
+ message="A store with that name already exists.",
+ )
+ except SQLAlchemyError:
+ abort(500, message="An error occurred creating the store.")
+
+ return store
diff --git a/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/end/schemas.py b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/end/schemas.py
new file mode 100644
index 00000000..2619c4ee
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/end/schemas.py
@@ -0,0 +1,26 @@
+from marshmallow import Schema, fields
+
+
+class PlainItemSchema(Schema):
+ id = fields.Int(dump_only=True)
+ name = fields.Str(required=True)
+ price = fields.Float(required=True)
+
+
+class PlainStoreSchema(Schema):
+ id = fields.Int(dump_only=True)
+ name = fields.Str()
+
+
+class ItemSchema(PlainItemSchema):
+ store_id = fields.Int(required=True, load_only=True)
+ store = fields.Nested(lambda: PlainStoreSchema(), dump_only=True)
+
+
+class ItemUpdateSchema(Schema):
+ name = fields.Str()
+ price = fields.Float()
+
+
+class StoreSchema(PlainStoreSchema):
+ items = fields.List(fields.Nested(PlainItemSchema()), dump_only=True)
diff --git a/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/start/.flaskenv b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/start/.flaskenv
new file mode 100644
index 00000000..75473901
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/start/.flaskenv
@@ -0,0 +1,2 @@
+FLASK_APP=app
+FLASK_ENV=development
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/start/Dockerfile b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/start/Dockerfile
new file mode 100644
index 00000000..652afba1
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/start/Dockerfile
@@ -0,0 +1,7 @@
+FROM python:3.10
+EXPOSE 5000
+WORKDIR /app
+COPY ./requirements.txt requirements.txt
+RUN pip install --no-cache-dir --upgrade -r requirements.txt
+COPY . .
+CMD ["flask", "run", "--host", "0.0.0.0"]
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/start/app.py b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/start/app.py
new file mode 100644
index 00000000..f3f475ca
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/start/app.py
@@ -0,0 +1,35 @@
+from flask import Flask
+from flask_smorest import Api
+
+from db import db
+
+import models
+
+from resources.item import blp as ItemBlueprint
+from resources.store import blp as StoreBlueprint
+
+
+def create_app(db_url=None):
+ app = Flask(__name__)
+ app.config["API_TITLE"] = "Stores REST API"
+ app.config["API_VERSION"] = "v1"
+ app.config["OPENAPI_VERSION"] = "3.0.3"
+ app.config["OPENAPI_URL_PREFIX"] = "/"
+ app.config["OPENAPI_SWAGGER_UI_PATH"] = "/swagger-ui"
+ app.config[
+ "OPENAPI_SWAGGER_UI_URL"
+ ] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist/"
+ app.config["SQLALCHEMY_DATABASE_URI"] = db_url or "sqlite:///data.db"
+ app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
+ app.config["PROPAGATE_EXCEPTIONS"] = True
+ db.init_app(app)
+ api = Api(app)
+
+ @app.before_first_request
+ def create_tables():
+ db.create_all()
+
+ api.register_blueprint(ItemBlueprint)
+ api.register_blueprint(StoreBlueprint)
+
+ return app
diff --git a/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/start/db.py b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/start/db.py
new file mode 100644
index 00000000..f0b13d6f
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/start/db.py
@@ -0,0 +1,3 @@
+from flask_sqlalchemy import SQLAlchemy
+
+db = SQLAlchemy()
diff --git a/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/start/models/__init__.py b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/start/models/__init__.py
new file mode 100644
index 00000000..b57f3f8a
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/start/models/__init__.py
@@ -0,0 +1,5 @@
+from models.user import UserModel
+from models.item import ItemModel
+from models.tag import TagModel
+from models.store import StoreModel
+from models.item_tags import ItemsTags
diff --git a/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/start/models/item.py b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/start/models/item.py
new file mode 100644
index 00000000..74797e0e
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/start/models/item.py
@@ -0,0 +1,14 @@
+from db import db
+
+
+class ItemModel(db.Model):
+ __tablename__ = "items"
+
+ id = db.Column(db.Integer, primary_key=True)
+ name = db.Column(db.String(80), unique=True, nullable=False)
+ price = db.Column(db.Float(precision=2), unique=False, nullable=False)
+
+ store_id = db.Column(
+ db.Integer, db.ForeignKey("stores.id"), unique=False, nullable=False
+ )
+ store = db.relationship("StoreModel", back_populates="items")
diff --git a/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/start/models/store.py b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/start/models/store.py
new file mode 100644
index 00000000..699147f9
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/start/models/store.py
@@ -0,0 +1,10 @@
+from db import db
+
+
+class StoreModel(db.Model):
+ __tablename__ = "stores"
+
+ id = db.Column(db.Integer, primary_key=True)
+ name = db.Column(db.String(80), unique=True, nullable=False)
+
+ items = db.relationship("ItemModel", back_populates="store", lazy="dynamic")
diff --git a/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/start/requirements.txt b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/start/requirements.txt
new file mode 100644
index 00000000..4764bf34
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/start/requirements.txt
@@ -0,0 +1,4 @@
+flask
+flask-smorest
+python-dotenv
+marshmallow
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/start/resources/__init__.py b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/start/resources/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/start/resources/item.py b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/start/resources/item.py
new file mode 100644
index 00000000..92e51bd6
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/start/resources/item.py
@@ -0,0 +1,44 @@
+from flask.views import MethodView
+from flask_smorest import Blueprint, abort
+from sqlalchemy.exc import SQLAlchemyError
+
+from db import db
+from models import ItemModel
+from schemas import ItemSchema, ItemUpdateSchema
+
+blp = Blueprint("Items", "items", description="Operations on items")
+
+
+@blp.route("/items/")
+class Item(MethodView):
+ @blp.response(200, ItemSchema)
+ def get(self, item_id):
+ raise NotImplementedError("Getting an item is not implemented.")
+
+ def delete(self, item_id):
+ raise NotImplementedError("Deleting an item is not implemented.")
+
+ @blp.arguments(ItemUpdateSchema)
+ @blp.response(200, ItemSchema)
+ def put(self, item_data, item_id):
+ raise NotImplementedError("Updating an item is not implemented.")
+
+
+@blp.route("/items")
+class ItemList(MethodView):
+ @blp.response(200, ItemSchema(many=True))
+ def get(self):
+ raise NotImplementedError("Listing items is not implemented.")
+
+ @blp.arguments(ItemSchema)
+ @blp.response(201, ItemSchema)
+ def post(self, item_data):
+ item = ItemModel(**item_data)
+
+ try:
+ db.session.add(item)
+ db.session.commit()
+ except SQLAlchemyError:
+ abort(500, message="An error occurred while inserting the item.")
+
+ return item
diff --git a/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/start/resources/store.py b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/start/resources/store.py
new file mode 100644
index 00000000..c04e6a05
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/start/resources/store.py
@@ -0,0 +1,44 @@
+from flask.views import MethodView
+from flask_smorest import Blueprint, abort
+from sqlalchemy.exc import SQLAlchemyError, IntegrityError
+
+from db import db
+from models import StoreModel
+from schemas import StoreSchema
+
+
+blp = Blueprint("Stores", "stores", description="Operations on stores")
+
+
+@blp.route("/stores/")
+class Store(MethodView):
+ @blp.response(200, StoreSchema)
+ def get(self, store_id):
+ raise NotImplementedError("Getting a store is not implemented.")
+
+ def delete(self, store_id):
+ raise NotImplementedError("Deleting a store is not implemented.")
+
+
+@blp.route("/stores")
+class StoreList(MethodView):
+ @blp.response(200, ItemSchema(many=True))
+ def get(self):
+ raise NotImplementedError("Listing stores is not implemented.")
+
+ @blp.arguments(StoreSchema)
+ @blp.response(201, StoreSchema)
+ def post(self, store_data):
+ store = StoreModel(**store_data)
+ try:
+ db.session.add(store)
+ db.session.commit()
+ except IntegrityError:
+ abort(
+ 400,
+ message="A store with that name already exists.",
+ )
+ except SQLAlchemyError:
+ abort(500, message="An error occurred creating the store.")
+
+ return store
diff --git a/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/start/schemas.py b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/start/schemas.py
new file mode 100644
index 00000000..2619c4ee
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/06_get_models_or_404/start/schemas.py
@@ -0,0 +1,26 @@
+from marshmallow import Schema, fields
+
+
+class PlainItemSchema(Schema):
+ id = fields.Int(dump_only=True)
+ name = fields.Str(required=True)
+ price = fields.Float(required=True)
+
+
+class PlainStoreSchema(Schema):
+ id = fields.Int(dump_only=True)
+ name = fields.Str()
+
+
+class ItemSchema(PlainItemSchema):
+ store_id = fields.Int(required=True, load_only=True)
+ store = fields.Nested(lambda: PlainStoreSchema(), dump_only=True)
+
+
+class ItemUpdateSchema(Schema):
+ name = fields.Str()
+ price = fields.Float()
+
+
+class StoreSchema(PlainStoreSchema):
+ items = fields.List(fields.Nested(PlainItemSchema()), dump_only=True)
diff --git a/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/README.md b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/README.md
new file mode 100644
index 00000000..bfaa8c81
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/README.md
@@ -0,0 +1,37 @@
+---
+title: Updating models with SQLAlchemy
+description: How to make changes to an existing model, or insert one if it doesn't already exist.
+---
+
+# Updating models with SQLAlchemy
+
+- [x] Set metadata above
+- [x] Start writing!
+- [x] Create `start` folder
+- [x] Create `end` folder
+- [ ] Create per-file diff between `end` and `start` (use "Compare Folders")
+
+A frequent operation in REST APIs is the "upsert", or "update or insert".
+
+This is an idempotent operation where we send the data we want the API to store. If the data identifier already exists, an update is done. If it doesn't, it is created.
+
+This idempotency is frequently seen with `PUT` requests. You can see it in action here:
+
+```python title="resources/item.py"
+@blp.arguments(ItemUpdateSchema)
+@blp.response(200, ItemSchema)
+def put(self, item_data, item_id):
+ item = ItemModel.query.get_or_404(item_id)
+ # highlight-start
+ if item:
+ item.price = item_data["price"]
+ item.name = item_data["name"]
+ else:
+ item = ItemModel(**item_data)
+
+ db.session.add(item)
+ db.session.commit()
+
+ return item
+ # highlight-end
+```
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/end/.flaskenv b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/end/.flaskenv
new file mode 100644
index 00000000..75473901
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/end/.flaskenv
@@ -0,0 +1,2 @@
+FLASK_APP=app
+FLASK_ENV=development
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/end/Dockerfile b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/end/Dockerfile
new file mode 100644
index 00000000..652afba1
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/end/Dockerfile
@@ -0,0 +1,7 @@
+FROM python:3.10
+EXPOSE 5000
+WORKDIR /app
+COPY ./requirements.txt requirements.txt
+RUN pip install --no-cache-dir --upgrade -r requirements.txt
+COPY . .
+CMD ["flask", "run", "--host", "0.0.0.0"]
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/end/app.py b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/end/app.py
new file mode 100644
index 00000000..f3f475ca
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/end/app.py
@@ -0,0 +1,35 @@
+from flask import Flask
+from flask_smorest import Api
+
+from db import db
+
+import models
+
+from resources.item import blp as ItemBlueprint
+from resources.store import blp as StoreBlueprint
+
+
+def create_app(db_url=None):
+ app = Flask(__name__)
+ app.config["API_TITLE"] = "Stores REST API"
+ app.config["API_VERSION"] = "v1"
+ app.config["OPENAPI_VERSION"] = "3.0.3"
+ app.config["OPENAPI_URL_PREFIX"] = "/"
+ app.config["OPENAPI_SWAGGER_UI_PATH"] = "/swagger-ui"
+ app.config[
+ "OPENAPI_SWAGGER_UI_URL"
+ ] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist/"
+ app.config["SQLALCHEMY_DATABASE_URI"] = db_url or "sqlite:///data.db"
+ app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
+ app.config["PROPAGATE_EXCEPTIONS"] = True
+ db.init_app(app)
+ api = Api(app)
+
+ @app.before_first_request
+ def create_tables():
+ db.create_all()
+
+ api.register_blueprint(ItemBlueprint)
+ api.register_blueprint(StoreBlueprint)
+
+ return app
diff --git a/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/end/db.py b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/end/db.py
new file mode 100644
index 00000000..f0b13d6f
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/end/db.py
@@ -0,0 +1,3 @@
+from flask_sqlalchemy import SQLAlchemy
+
+db = SQLAlchemy()
diff --git a/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/end/models/__init__.py b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/end/models/__init__.py
new file mode 100644
index 00000000..b57f3f8a
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/end/models/__init__.py
@@ -0,0 +1,5 @@
+from models.user import UserModel
+from models.item import ItemModel
+from models.tag import TagModel
+from models.store import StoreModel
+from models.item_tags import ItemsTags
diff --git a/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/end/models/item.py b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/end/models/item.py
new file mode 100644
index 00000000..74797e0e
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/end/models/item.py
@@ -0,0 +1,14 @@
+from db import db
+
+
+class ItemModel(db.Model):
+ __tablename__ = "items"
+
+ id = db.Column(db.Integer, primary_key=True)
+ name = db.Column(db.String(80), unique=True, nullable=False)
+ price = db.Column(db.Float(precision=2), unique=False, nullable=False)
+
+ store_id = db.Column(
+ db.Integer, db.ForeignKey("stores.id"), unique=False, nullable=False
+ )
+ store = db.relationship("StoreModel", back_populates="items")
diff --git a/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/end/models/store.py b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/end/models/store.py
new file mode 100644
index 00000000..699147f9
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/end/models/store.py
@@ -0,0 +1,10 @@
+from db import db
+
+
+class StoreModel(db.Model):
+ __tablename__ = "stores"
+
+ id = db.Column(db.Integer, primary_key=True)
+ name = db.Column(db.String(80), unique=True, nullable=False)
+
+ items = db.relationship("ItemModel", back_populates="store", lazy="dynamic")
diff --git a/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/end/requirements.txt b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/end/requirements.txt
new file mode 100644
index 00000000..4764bf34
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/end/requirements.txt
@@ -0,0 +1,4 @@
+flask
+flask-smorest
+python-dotenv
+marshmallow
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/end/resources/__init__.py b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/end/resources/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/end/resources/item.py b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/end/resources/item.py
new file mode 100644
index 00000000..285f8557
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/end/resources/item.py
@@ -0,0 +1,57 @@
+from flask.views import MethodView
+from flask_smorest import Blueprint, abort
+from sqlalchemy.exc import SQLAlchemyError
+
+from db import db
+from models import ItemModel
+from schemas import ItemSchema, ItemUpdateSchema
+
+blp = Blueprint("Items", "items", description="Operations on items")
+
+
+@blp.route("/items/")
+class Item(MethodView):
+ @blp.response(200, ItemSchema)
+ def get(self, item_id):
+ item = ItemModel.query.get_or_404(item_id)
+ return item
+
+ def delete(self, item_id):
+ item = ItemModel.query.get_or_404(item_id)
+ raise NotImplementedError("Deleting an item is not implemented.")
+
+ @blp.arguments(ItemUpdateSchema)
+ @blp.response(200, ItemSchema)
+ def put(self, item_data, item_id):
+ item = ItemModel.query.get_or_404(item_id)
+
+ if item:
+ item.price = item_data["price"]
+ item.name = item_data["name"]
+ else:
+ item = ItemModel(**item_data)
+
+ db.session.add(item)
+ db.session.commit()
+
+ return item
+
+
+@blp.route("/items")
+class ItemList(MethodView):
+ @blp.response(200, ItemSchema(many=True))
+ def get(self):
+ raise NotImplementedError("Listing items is not implemented.")
+
+ @blp.arguments(ItemSchema)
+ @blp.response(201, ItemSchema)
+ def post(self, item_data):
+ item = ItemModel(**item_data)
+
+ try:
+ db.session.add(item)
+ db.session.commit()
+ except SQLAlchemyError:
+ abort(500, message="An error occurred while inserting the item.")
+
+ return item
diff --git a/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/end/resources/store.py b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/end/resources/store.py
new file mode 100644
index 00000000..a750ae10
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/end/resources/store.py
@@ -0,0 +1,46 @@
+from flask.views import MethodView
+from flask_smorest import Blueprint, abort
+from sqlalchemy.exc import SQLAlchemyError, IntegrityError
+
+from db import db
+from models import StoreModel
+from schemas import StoreSchema
+
+
+blp = Blueprint("Stores", "stores", description="Operations on stores")
+
+
+@blp.route("/stores/")
+class Store(MethodView):
+ @blp.response(200, StoreSchema)
+ def get(self, store_id):
+ store = StoreModel.query.get_or_404(store_id)
+ return store
+
+ def delete(self, store_id):
+ store = StoreModel.query.get_or_404(store_id)
+ raise NotImplementedError("Deleting a store is not implemented.")
+
+
+@blp.route("/stores")
+class StoreList(MethodView):
+ @blp.response(200, ItemSchema(many=True))
+ def get(self):
+ raise NotImplementedError("Listing stores is not implemented.")
+
+ @blp.arguments(StoreSchema)
+ @blp.response(201, StoreSchema)
+ def post(self, store_data):
+ store = StoreModel(**store_data)
+ try:
+ db.session.add(store)
+ db.session.commit()
+ except IntegrityError:
+ abort(
+ 400,
+ message="A store with that name already exists.",
+ )
+ except SQLAlchemyError:
+ abort(500, message="An error occurred creating the store.")
+
+ return store
diff --git a/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/end/schemas.py b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/end/schemas.py
new file mode 100644
index 00000000..2619c4ee
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/end/schemas.py
@@ -0,0 +1,26 @@
+from marshmallow import Schema, fields
+
+
+class PlainItemSchema(Schema):
+ id = fields.Int(dump_only=True)
+ name = fields.Str(required=True)
+ price = fields.Float(required=True)
+
+
+class PlainStoreSchema(Schema):
+ id = fields.Int(dump_only=True)
+ name = fields.Str()
+
+
+class ItemSchema(PlainItemSchema):
+ store_id = fields.Int(required=True, load_only=True)
+ store = fields.Nested(lambda: PlainStoreSchema(), dump_only=True)
+
+
+class ItemUpdateSchema(Schema):
+ name = fields.Str()
+ price = fields.Float()
+
+
+class StoreSchema(PlainStoreSchema):
+ items = fields.List(fields.Nested(PlainItemSchema()), dump_only=True)
diff --git a/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/start/.flaskenv b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/start/.flaskenv
new file mode 100644
index 00000000..75473901
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/start/.flaskenv
@@ -0,0 +1,2 @@
+FLASK_APP=app
+FLASK_ENV=development
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/start/Dockerfile b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/start/Dockerfile
new file mode 100644
index 00000000..652afba1
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/start/Dockerfile
@@ -0,0 +1,7 @@
+FROM python:3.10
+EXPOSE 5000
+WORKDIR /app
+COPY ./requirements.txt requirements.txt
+RUN pip install --no-cache-dir --upgrade -r requirements.txt
+COPY . .
+CMD ["flask", "run", "--host", "0.0.0.0"]
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/start/app.py b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/start/app.py
new file mode 100644
index 00000000..f3f475ca
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/start/app.py
@@ -0,0 +1,35 @@
+from flask import Flask
+from flask_smorest import Api
+
+from db import db
+
+import models
+
+from resources.item import blp as ItemBlueprint
+from resources.store import blp as StoreBlueprint
+
+
+def create_app(db_url=None):
+ app = Flask(__name__)
+ app.config["API_TITLE"] = "Stores REST API"
+ app.config["API_VERSION"] = "v1"
+ app.config["OPENAPI_VERSION"] = "3.0.3"
+ app.config["OPENAPI_URL_PREFIX"] = "/"
+ app.config["OPENAPI_SWAGGER_UI_PATH"] = "/swagger-ui"
+ app.config[
+ "OPENAPI_SWAGGER_UI_URL"
+ ] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist/"
+ app.config["SQLALCHEMY_DATABASE_URI"] = db_url or "sqlite:///data.db"
+ app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
+ app.config["PROPAGATE_EXCEPTIONS"] = True
+ db.init_app(app)
+ api = Api(app)
+
+ @app.before_first_request
+ def create_tables():
+ db.create_all()
+
+ api.register_blueprint(ItemBlueprint)
+ api.register_blueprint(StoreBlueprint)
+
+ return app
diff --git a/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/start/db.py b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/start/db.py
new file mode 100644
index 00000000..f0b13d6f
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/start/db.py
@@ -0,0 +1,3 @@
+from flask_sqlalchemy import SQLAlchemy
+
+db = SQLAlchemy()
diff --git a/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/start/models/__init__.py b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/start/models/__init__.py
new file mode 100644
index 00000000..b57f3f8a
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/start/models/__init__.py
@@ -0,0 +1,5 @@
+from models.user import UserModel
+from models.item import ItemModel
+from models.tag import TagModel
+from models.store import StoreModel
+from models.item_tags import ItemsTags
diff --git a/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/start/models/item.py b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/start/models/item.py
new file mode 100644
index 00000000..74797e0e
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/start/models/item.py
@@ -0,0 +1,14 @@
+from db import db
+
+
+class ItemModel(db.Model):
+ __tablename__ = "items"
+
+ id = db.Column(db.Integer, primary_key=True)
+ name = db.Column(db.String(80), unique=True, nullable=False)
+ price = db.Column(db.Float(precision=2), unique=False, nullable=False)
+
+ store_id = db.Column(
+ db.Integer, db.ForeignKey("stores.id"), unique=False, nullable=False
+ )
+ store = db.relationship("StoreModel", back_populates="items")
diff --git a/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/start/models/store.py b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/start/models/store.py
new file mode 100644
index 00000000..699147f9
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/start/models/store.py
@@ -0,0 +1,10 @@
+from db import db
+
+
+class StoreModel(db.Model):
+ __tablename__ = "stores"
+
+ id = db.Column(db.Integer, primary_key=True)
+ name = db.Column(db.String(80), unique=True, nullable=False)
+
+ items = db.relationship("ItemModel", back_populates="store", lazy="dynamic")
diff --git a/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/start/requirements.txt b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/start/requirements.txt
new file mode 100644
index 00000000..4764bf34
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/start/requirements.txt
@@ -0,0 +1,4 @@
+flask
+flask-smorest
+python-dotenv
+marshmallow
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/start/resources/__init__.py b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/start/resources/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/start/resources/item.py b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/start/resources/item.py
new file mode 100644
index 00000000..26226af2
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/start/resources/item.py
@@ -0,0 +1,48 @@
+from flask.views import MethodView
+from flask_smorest import Blueprint, abort
+from sqlalchemy.exc import SQLAlchemyError
+
+from db import db
+from models import ItemModel
+from schemas import ItemSchema, ItemUpdateSchema
+
+blp = Blueprint("Items", "items", description="Operations on items")
+
+
+@blp.route("/items/")
+class Item(MethodView):
+ @blp.response(200, ItemSchema)
+ def get(self, item_id):
+ item = ItemModel.query.get_or_404(item_id)
+ return item
+
+ def delete(self, item_id):
+ item = ItemModel.query.get_or_404(item_id)
+ raise NotImplementedError("Deleting an item is not implemented.")
+
+ @blp.arguments(ItemUpdateSchema)
+ @blp.response(200, ItemSchema)
+ def put(self, item_data, item_id):
+ item = ItemModel.query.get_or_404(item_id)
+
+ raise NotImplementedError("Updating an item is not implemented.")
+
+
+@blp.route("/items")
+class ItemList(MethodView):
+ @blp.response(200, ItemSchema(many=True))
+ def get(self):
+ raise NotImplementedError("Listing items is not implemented.")
+
+ @blp.arguments(ItemSchema)
+ @blp.response(201, ItemSchema)
+ def post(self, item_data):
+ item = ItemModel(**item_data)
+
+ try:
+ db.session.add(item)
+ db.session.commit()
+ except SQLAlchemyError:
+ abort(500, message="An error occurred while inserting the item.")
+
+ return item
diff --git a/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/start/resources/store.py b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/start/resources/store.py
new file mode 100644
index 00000000..a750ae10
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/start/resources/store.py
@@ -0,0 +1,46 @@
+from flask.views import MethodView
+from flask_smorest import Blueprint, abort
+from sqlalchemy.exc import SQLAlchemyError, IntegrityError
+
+from db import db
+from models import StoreModel
+from schemas import StoreSchema
+
+
+blp = Blueprint("Stores", "stores", description="Operations on stores")
+
+
+@blp.route("/stores/")
+class Store(MethodView):
+ @blp.response(200, StoreSchema)
+ def get(self, store_id):
+ store = StoreModel.query.get_or_404(store_id)
+ return store
+
+ def delete(self, store_id):
+ store = StoreModel.query.get_or_404(store_id)
+ raise NotImplementedError("Deleting a store is not implemented.")
+
+
+@blp.route("/stores")
+class StoreList(MethodView):
+ @blp.response(200, ItemSchema(many=True))
+ def get(self):
+ raise NotImplementedError("Listing stores is not implemented.")
+
+ @blp.arguments(StoreSchema)
+ @blp.response(201, StoreSchema)
+ def post(self, store_data):
+ store = StoreModel(**store_data)
+ try:
+ db.session.add(store)
+ db.session.commit()
+ except IntegrityError:
+ abort(
+ 400,
+ message="A store with that name already exists.",
+ )
+ except SQLAlchemyError:
+ abort(500, message="An error occurred creating the store.")
+
+ return store
diff --git a/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/start/schemas.py b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/start/schemas.py
new file mode 100644
index 00000000..2619c4ee
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/07_updating_models_sqlalchemy/start/schemas.py
@@ -0,0 +1,26 @@
+from marshmallow import Schema, fields
+
+
+class PlainItemSchema(Schema):
+ id = fields.Int(dump_only=True)
+ name = fields.Str(required=True)
+ price = fields.Float(required=True)
+
+
+class PlainStoreSchema(Schema):
+ id = fields.Int(dump_only=True)
+ name = fields.Str()
+
+
+class ItemSchema(PlainItemSchema):
+ store_id = fields.Int(required=True, load_only=True)
+ store = fields.Nested(lambda: PlainStoreSchema(), dump_only=True)
+
+
+class ItemUpdateSchema(Schema):
+ name = fields.Str()
+ price = fields.Float()
+
+
+class StoreSchema(PlainStoreSchema):
+ items = fields.List(fields.Nested(PlainItemSchema()), dump_only=True)
diff --git a/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/README.md b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/README.md
new file mode 100644
index 00000000..2ef0001a
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/README.md
@@ -0,0 +1,30 @@
+---
+title: Retrieve a list of all models
+description: Get more than one model and return it as a list from the API.
+---
+
+# Retrieve a list of all models
+
+- [x] Set metadata above
+- [x] Start writing!
+- [x] Create `start` folder
+- [x] Create `end` folder
+- [ ] Create per-file diff between `end` and `start` (use "Compare Folders")
+
+Using the `query` attribute of our model class, we can retrieve all the results of the query:
+
+```python title="resources/item.py"
+@blp.response(200, ItemSchema(many=True))
+def get(self):
+ # highlight-start
+ return ItemModel.query.all()
+ # highlight-end
+```
+
+```python title="resources/store.py"
+@blp.response(200, StoreSchema(many=True))
+def get(self):
+ # highlight-start
+ return StoreModel.query.all()
+ # highlight-end
+```
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/end/.flaskenv b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/end/.flaskenv
new file mode 100644
index 00000000..75473901
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/end/.flaskenv
@@ -0,0 +1,2 @@
+FLASK_APP=app
+FLASK_ENV=development
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/end/Dockerfile b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/end/Dockerfile
new file mode 100644
index 00000000..652afba1
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/end/Dockerfile
@@ -0,0 +1,7 @@
+FROM python:3.10
+EXPOSE 5000
+WORKDIR /app
+COPY ./requirements.txt requirements.txt
+RUN pip install --no-cache-dir --upgrade -r requirements.txt
+COPY . .
+CMD ["flask", "run", "--host", "0.0.0.0"]
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/end/app.py b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/end/app.py
new file mode 100644
index 00000000..f3f475ca
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/end/app.py
@@ -0,0 +1,35 @@
+from flask import Flask
+from flask_smorest import Api
+
+from db import db
+
+import models
+
+from resources.item import blp as ItemBlueprint
+from resources.store import blp as StoreBlueprint
+
+
+def create_app(db_url=None):
+ app = Flask(__name__)
+ app.config["API_TITLE"] = "Stores REST API"
+ app.config["API_VERSION"] = "v1"
+ app.config["OPENAPI_VERSION"] = "3.0.3"
+ app.config["OPENAPI_URL_PREFIX"] = "/"
+ app.config["OPENAPI_SWAGGER_UI_PATH"] = "/swagger-ui"
+ app.config[
+ "OPENAPI_SWAGGER_UI_URL"
+ ] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist/"
+ app.config["SQLALCHEMY_DATABASE_URI"] = db_url or "sqlite:///data.db"
+ app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
+ app.config["PROPAGATE_EXCEPTIONS"] = True
+ db.init_app(app)
+ api = Api(app)
+
+ @app.before_first_request
+ def create_tables():
+ db.create_all()
+
+ api.register_blueprint(ItemBlueprint)
+ api.register_blueprint(StoreBlueprint)
+
+ return app
diff --git a/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/end/db.py b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/end/db.py
new file mode 100644
index 00000000..f0b13d6f
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/end/db.py
@@ -0,0 +1,3 @@
+from flask_sqlalchemy import SQLAlchemy
+
+db = SQLAlchemy()
diff --git a/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/end/models/__init__.py b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/end/models/__init__.py
new file mode 100644
index 00000000..b57f3f8a
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/end/models/__init__.py
@@ -0,0 +1,5 @@
+from models.user import UserModel
+from models.item import ItemModel
+from models.tag import TagModel
+from models.store import StoreModel
+from models.item_tags import ItemsTags
diff --git a/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/end/models/item.py b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/end/models/item.py
new file mode 100644
index 00000000..74797e0e
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/end/models/item.py
@@ -0,0 +1,14 @@
+from db import db
+
+
+class ItemModel(db.Model):
+ __tablename__ = "items"
+
+ id = db.Column(db.Integer, primary_key=True)
+ name = db.Column(db.String(80), unique=True, nullable=False)
+ price = db.Column(db.Float(precision=2), unique=False, nullable=False)
+
+ store_id = db.Column(
+ db.Integer, db.ForeignKey("stores.id"), unique=False, nullable=False
+ )
+ store = db.relationship("StoreModel", back_populates="items")
diff --git a/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/end/models/store.py b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/end/models/store.py
new file mode 100644
index 00000000..699147f9
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/end/models/store.py
@@ -0,0 +1,10 @@
+from db import db
+
+
+class StoreModel(db.Model):
+ __tablename__ = "stores"
+
+ id = db.Column(db.Integer, primary_key=True)
+ name = db.Column(db.String(80), unique=True, nullable=False)
+
+ items = db.relationship("ItemModel", back_populates="store", lazy="dynamic")
diff --git a/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/end/requirements.txt b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/end/requirements.txt
new file mode 100644
index 00000000..4764bf34
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/end/requirements.txt
@@ -0,0 +1,4 @@
+flask
+flask-smorest
+python-dotenv
+marshmallow
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/end/resources/__init__.py b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/end/resources/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/end/resources/item.py b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/end/resources/item.py
new file mode 100644
index 00000000..e7b7f053
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/end/resources/item.py
@@ -0,0 +1,57 @@
+from flask.views import MethodView
+from flask_smorest import Blueprint, abort
+from sqlalchemy.exc import SQLAlchemyError
+
+from db import db
+from models import ItemModel
+from schemas import ItemSchema, ItemUpdateSchema
+
+blp = Blueprint("Items", "items", description="Operations on items")
+
+
+@blp.route("/items/")
+class Item(MethodView):
+ @blp.response(200, ItemSchema)
+ def get(self, item_id):
+ item = ItemModel.query.get_or_404(item_id)
+ return item
+
+ def delete(self, item_id):
+ item = ItemModel.query.get_or_404(item_id)
+ raise NotImplementedError("Deleting an item is not implemented.")
+
+ @blp.arguments(ItemUpdateSchema)
+ @blp.response(200, ItemSchema)
+ def put(self, item_data, item_id):
+ item = ItemModel.query.get_or_404(item_id)
+
+ if item:
+ item.price = item_data["price"]
+ item.name = item_data["name"]
+ else:
+ item = ItemModel(**item_data)
+
+ db.session.add(item)
+ db.session.commit()
+
+ return item
+
+
+@blp.route("/items")
+class ItemList(MethodView):
+ @blp.response(200, ItemSchema(many=True))
+ def get(self):
+ return ItemModel.query.all()
+
+ @blp.arguments(ItemSchema)
+ @blp.response(201, ItemSchema)
+ def post(self, item_data):
+ item = ItemModel(**item_data)
+
+ try:
+ db.session.add(item)
+ db.session.commit()
+ except SQLAlchemyError:
+ abort(500, message="An error occurred while inserting the item.")
+
+ return item
diff --git a/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/end/resources/store.py b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/end/resources/store.py
new file mode 100644
index 00000000..0cd67a95
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/end/resources/store.py
@@ -0,0 +1,46 @@
+from flask.views import MethodView
+from flask_smorest import Blueprint, abort
+from sqlalchemy.exc import SQLAlchemyError, IntegrityError
+
+from db import db
+from models import StoreModel
+from schemas import StoreSchema
+
+
+blp = Blueprint("Stores", "stores", description="Operations on stores")
+
+
+@blp.route("/stores/")
+class Store(MethodView):
+ @blp.response(200, StoreSchema)
+ def get(self, store_id):
+ store = StoreModel.query.get_or_404(store_id)
+ return store
+
+ def delete(self, store_id):
+ store = StoreModel.query.get_or_404(store_id)
+ raise NotImplementedError("Deleting a store is not implemented.")
+
+
+@blp.route("/stores")
+class StoreList(MethodView):
+ @blp.response(200, StoreSchema(many=True))
+ def get(self):
+ return StoreModel.query.all()
+
+ @blp.arguments(StoreSchema)
+ @blp.response(201, StoreSchema)
+ def post(self, store_data):
+ store = StoreModel(**store_data)
+ try:
+ db.session.add(store)
+ db.session.commit()
+ except IntegrityError:
+ abort(
+ 400,
+ message="A store with that name already exists.",
+ )
+ except SQLAlchemyError:
+ abort(500, message="An error occurred creating the store.")
+
+ return store
diff --git a/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/end/schemas.py b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/end/schemas.py
new file mode 100644
index 00000000..2619c4ee
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/end/schemas.py
@@ -0,0 +1,26 @@
+from marshmallow import Schema, fields
+
+
+class PlainItemSchema(Schema):
+ id = fields.Int(dump_only=True)
+ name = fields.Str(required=True)
+ price = fields.Float(required=True)
+
+
+class PlainStoreSchema(Schema):
+ id = fields.Int(dump_only=True)
+ name = fields.Str()
+
+
+class ItemSchema(PlainItemSchema):
+ store_id = fields.Int(required=True, load_only=True)
+ store = fields.Nested(lambda: PlainStoreSchema(), dump_only=True)
+
+
+class ItemUpdateSchema(Schema):
+ name = fields.Str()
+ price = fields.Float()
+
+
+class StoreSchema(PlainStoreSchema):
+ items = fields.List(fields.Nested(PlainItemSchema()), dump_only=True)
diff --git a/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/start/.flaskenv b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/start/.flaskenv
new file mode 100644
index 00000000..75473901
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/start/.flaskenv
@@ -0,0 +1,2 @@
+FLASK_APP=app
+FLASK_ENV=development
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/start/Dockerfile b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/start/Dockerfile
new file mode 100644
index 00000000..652afba1
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/start/Dockerfile
@@ -0,0 +1,7 @@
+FROM python:3.10
+EXPOSE 5000
+WORKDIR /app
+COPY ./requirements.txt requirements.txt
+RUN pip install --no-cache-dir --upgrade -r requirements.txt
+COPY . .
+CMD ["flask", "run", "--host", "0.0.0.0"]
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/start/app.py b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/start/app.py
new file mode 100644
index 00000000..f3f475ca
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/start/app.py
@@ -0,0 +1,35 @@
+from flask import Flask
+from flask_smorest import Api
+
+from db import db
+
+import models
+
+from resources.item import blp as ItemBlueprint
+from resources.store import blp as StoreBlueprint
+
+
+def create_app(db_url=None):
+ app = Flask(__name__)
+ app.config["API_TITLE"] = "Stores REST API"
+ app.config["API_VERSION"] = "v1"
+ app.config["OPENAPI_VERSION"] = "3.0.3"
+ app.config["OPENAPI_URL_PREFIX"] = "/"
+ app.config["OPENAPI_SWAGGER_UI_PATH"] = "/swagger-ui"
+ app.config[
+ "OPENAPI_SWAGGER_UI_URL"
+ ] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist/"
+ app.config["SQLALCHEMY_DATABASE_URI"] = db_url or "sqlite:///data.db"
+ app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
+ app.config["PROPAGATE_EXCEPTIONS"] = True
+ db.init_app(app)
+ api = Api(app)
+
+ @app.before_first_request
+ def create_tables():
+ db.create_all()
+
+ api.register_blueprint(ItemBlueprint)
+ api.register_blueprint(StoreBlueprint)
+
+ return app
diff --git a/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/start/db.py b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/start/db.py
new file mode 100644
index 00000000..f0b13d6f
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/start/db.py
@@ -0,0 +1,3 @@
+from flask_sqlalchemy import SQLAlchemy
+
+db = SQLAlchemy()
diff --git a/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/start/models/__init__.py b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/start/models/__init__.py
new file mode 100644
index 00000000..b57f3f8a
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/start/models/__init__.py
@@ -0,0 +1,5 @@
+from models.user import UserModel
+from models.item import ItemModel
+from models.tag import TagModel
+from models.store import StoreModel
+from models.item_tags import ItemsTags
diff --git a/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/start/models/item.py b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/start/models/item.py
new file mode 100644
index 00000000..74797e0e
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/start/models/item.py
@@ -0,0 +1,14 @@
+from db import db
+
+
+class ItemModel(db.Model):
+ __tablename__ = "items"
+
+ id = db.Column(db.Integer, primary_key=True)
+ name = db.Column(db.String(80), unique=True, nullable=False)
+ price = db.Column(db.Float(precision=2), unique=False, nullable=False)
+
+ store_id = db.Column(
+ db.Integer, db.ForeignKey("stores.id"), unique=False, nullable=False
+ )
+ store = db.relationship("StoreModel", back_populates="items")
diff --git a/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/start/models/store.py b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/start/models/store.py
new file mode 100644
index 00000000..699147f9
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/start/models/store.py
@@ -0,0 +1,10 @@
+from db import db
+
+
+class StoreModel(db.Model):
+ __tablename__ = "stores"
+
+ id = db.Column(db.Integer, primary_key=True)
+ name = db.Column(db.String(80), unique=True, nullable=False)
+
+ items = db.relationship("ItemModel", back_populates="store", lazy="dynamic")
diff --git a/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/start/requirements.txt b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/start/requirements.txt
new file mode 100644
index 00000000..4764bf34
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/start/requirements.txt
@@ -0,0 +1,4 @@
+flask
+flask-smorest
+python-dotenv
+marshmallow
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/start/resources/__init__.py b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/start/resources/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/start/resources/item.py b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/start/resources/item.py
new file mode 100644
index 00000000..285f8557
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/start/resources/item.py
@@ -0,0 +1,57 @@
+from flask.views import MethodView
+from flask_smorest import Blueprint, abort
+from sqlalchemy.exc import SQLAlchemyError
+
+from db import db
+from models import ItemModel
+from schemas import ItemSchema, ItemUpdateSchema
+
+blp = Blueprint("Items", "items", description="Operations on items")
+
+
+@blp.route("/items/")
+class Item(MethodView):
+ @blp.response(200, ItemSchema)
+ def get(self, item_id):
+ item = ItemModel.query.get_or_404(item_id)
+ return item
+
+ def delete(self, item_id):
+ item = ItemModel.query.get_or_404(item_id)
+ raise NotImplementedError("Deleting an item is not implemented.")
+
+ @blp.arguments(ItemUpdateSchema)
+ @blp.response(200, ItemSchema)
+ def put(self, item_data, item_id):
+ item = ItemModel.query.get_or_404(item_id)
+
+ if item:
+ item.price = item_data["price"]
+ item.name = item_data["name"]
+ else:
+ item = ItemModel(**item_data)
+
+ db.session.add(item)
+ db.session.commit()
+
+ return item
+
+
+@blp.route("/items")
+class ItemList(MethodView):
+ @blp.response(200, ItemSchema(many=True))
+ def get(self):
+ raise NotImplementedError("Listing items is not implemented.")
+
+ @blp.arguments(ItemSchema)
+ @blp.response(201, ItemSchema)
+ def post(self, item_data):
+ item = ItemModel(**item_data)
+
+ try:
+ db.session.add(item)
+ db.session.commit()
+ except SQLAlchemyError:
+ abort(500, message="An error occurred while inserting the item.")
+
+ return item
diff --git a/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/start/resources/store.py b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/start/resources/store.py
new file mode 100644
index 00000000..a750ae10
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/start/resources/store.py
@@ -0,0 +1,46 @@
+from flask.views import MethodView
+from flask_smorest import Blueprint, abort
+from sqlalchemy.exc import SQLAlchemyError, IntegrityError
+
+from db import db
+from models import StoreModel
+from schemas import StoreSchema
+
+
+blp = Blueprint("Stores", "stores", description="Operations on stores")
+
+
+@blp.route("/stores/")
+class Store(MethodView):
+ @blp.response(200, StoreSchema)
+ def get(self, store_id):
+ store = StoreModel.query.get_or_404(store_id)
+ return store
+
+ def delete(self, store_id):
+ store = StoreModel.query.get_or_404(store_id)
+ raise NotImplementedError("Deleting a store is not implemented.")
+
+
+@blp.route("/stores")
+class StoreList(MethodView):
+ @blp.response(200, ItemSchema(many=True))
+ def get(self):
+ raise NotImplementedError("Listing stores is not implemented.")
+
+ @blp.arguments(StoreSchema)
+ @blp.response(201, StoreSchema)
+ def post(self, store_data):
+ store = StoreModel(**store_data)
+ try:
+ db.session.add(store)
+ db.session.commit()
+ except IntegrityError:
+ abort(
+ 400,
+ message="A store with that name already exists.",
+ )
+ except SQLAlchemyError:
+ abort(500, message="An error occurred creating the store.")
+
+ return store
diff --git a/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/start/schemas.py b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/start/schemas.py
new file mode 100644
index 00000000..2619c4ee
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/08_retrieve_list_all_models/start/schemas.py
@@ -0,0 +1,26 @@
+from marshmallow import Schema, fields
+
+
+class PlainItemSchema(Schema):
+ id = fields.Int(dump_only=True)
+ name = fields.Str(required=True)
+ price = fields.Float(required=True)
+
+
+class PlainStoreSchema(Schema):
+ id = fields.Int(dump_only=True)
+ name = fields.Str()
+
+
+class ItemSchema(PlainItemSchema):
+ store_id = fields.Int(required=True, load_only=True)
+ store = fields.Nested(lambda: PlainStoreSchema(), dump_only=True)
+
+
+class ItemUpdateSchema(Schema):
+ name = fields.Str()
+ price = fields.Float()
+
+
+class StoreSchema(PlainStoreSchema):
+ items = fields.List(fields.Nested(PlainItemSchema()), dump_only=True)
diff --git a/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/README.md b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/README.md
new file mode 100644
index 00000000..757a5844
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/README.md
@@ -0,0 +1,34 @@
+---
+title: Delete models with SQLAlchemy
+description: Use SQLAlchemy to handle removal of a specific model.
+---
+
+# Delete models with SQLAlchemy
+
+- [x] Set metadata above
+- [x] Start writing!
+- [x] Create `start` folder
+- [x] Create `end` folder
+- [ ] Create per-file diff between `end` and `start` (use "Compare Folders")
+
+Just as with adding, deleting models is a matter of using `db.session`, and then committing when the deletion is complete:
+
+```python title="resources/item.py"
+def delete(self, item_id):
+ item = ItemModel.query.get_or_404(item_id)
+ # highlight-start
+ db.session.delete(item)
+ db.session.commit()
+ return {"message": "Item deleted."}
+ # highlight-end
+```
+
+```python title="resources/store.py"
+def delete(self, store_id):
+ store = StoreModel.query.get_or_404(store_id)
+ # highlight-start
+ db.session.delete(store)
+ db.session.commit()
+ return {"message": "Store deleted"}, 200
+ # highlight-end
+```
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/end/.flaskenv b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/end/.flaskenv
new file mode 100644
index 00000000..75473901
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/end/.flaskenv
@@ -0,0 +1,2 @@
+FLASK_APP=app
+FLASK_ENV=development
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/end/Dockerfile b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/end/Dockerfile
new file mode 100644
index 00000000..652afba1
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/end/Dockerfile
@@ -0,0 +1,7 @@
+FROM python:3.10
+EXPOSE 5000
+WORKDIR /app
+COPY ./requirements.txt requirements.txt
+RUN pip install --no-cache-dir --upgrade -r requirements.txt
+COPY . .
+CMD ["flask", "run", "--host", "0.0.0.0"]
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/end/app.py b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/end/app.py
new file mode 100644
index 00000000..f3f475ca
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/end/app.py
@@ -0,0 +1,35 @@
+from flask import Flask
+from flask_smorest import Api
+
+from db import db
+
+import models
+
+from resources.item import blp as ItemBlueprint
+from resources.store import blp as StoreBlueprint
+
+
+def create_app(db_url=None):
+ app = Flask(__name__)
+ app.config["API_TITLE"] = "Stores REST API"
+ app.config["API_VERSION"] = "v1"
+ app.config["OPENAPI_VERSION"] = "3.0.3"
+ app.config["OPENAPI_URL_PREFIX"] = "/"
+ app.config["OPENAPI_SWAGGER_UI_PATH"] = "/swagger-ui"
+ app.config[
+ "OPENAPI_SWAGGER_UI_URL"
+ ] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist/"
+ app.config["SQLALCHEMY_DATABASE_URI"] = db_url or "sqlite:///data.db"
+ app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
+ app.config["PROPAGATE_EXCEPTIONS"] = True
+ db.init_app(app)
+ api = Api(app)
+
+ @app.before_first_request
+ def create_tables():
+ db.create_all()
+
+ api.register_blueprint(ItemBlueprint)
+ api.register_blueprint(StoreBlueprint)
+
+ return app
diff --git a/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/end/db.py b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/end/db.py
new file mode 100644
index 00000000..f0b13d6f
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/end/db.py
@@ -0,0 +1,3 @@
+from flask_sqlalchemy import SQLAlchemy
+
+db = SQLAlchemy()
diff --git a/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/end/models/__init__.py b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/end/models/__init__.py
new file mode 100644
index 00000000..b57f3f8a
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/end/models/__init__.py
@@ -0,0 +1,5 @@
+from models.user import UserModel
+from models.item import ItemModel
+from models.tag import TagModel
+from models.store import StoreModel
+from models.item_tags import ItemsTags
diff --git a/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/end/models/item.py b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/end/models/item.py
new file mode 100644
index 00000000..74797e0e
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/end/models/item.py
@@ -0,0 +1,14 @@
+from db import db
+
+
+class ItemModel(db.Model):
+ __tablename__ = "items"
+
+ id = db.Column(db.Integer, primary_key=True)
+ name = db.Column(db.String(80), unique=True, nullable=False)
+ price = db.Column(db.Float(precision=2), unique=False, nullable=False)
+
+ store_id = db.Column(
+ db.Integer, db.ForeignKey("stores.id"), unique=False, nullable=False
+ )
+ store = db.relationship("StoreModel", back_populates="items")
diff --git a/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/end/models/store.py b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/end/models/store.py
new file mode 100644
index 00000000..699147f9
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/end/models/store.py
@@ -0,0 +1,10 @@
+from db import db
+
+
+class StoreModel(db.Model):
+ __tablename__ = "stores"
+
+ id = db.Column(db.Integer, primary_key=True)
+ name = db.Column(db.String(80), unique=True, nullable=False)
+
+ items = db.relationship("ItemModel", back_populates="store", lazy="dynamic")
diff --git a/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/end/requirements.txt b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/end/requirements.txt
new file mode 100644
index 00000000..4764bf34
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/end/requirements.txt
@@ -0,0 +1,4 @@
+flask
+flask-smorest
+python-dotenv
+marshmallow
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/end/resources/__init__.py b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/end/resources/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/end/resources/item.py b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/end/resources/item.py
new file mode 100644
index 00000000..35190b12
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/end/resources/item.py
@@ -0,0 +1,59 @@
+from flask.views import MethodView
+from flask_smorest import Blueprint, abort
+from sqlalchemy.exc import SQLAlchemyError
+
+from db import db
+from models import ItemModel
+from schemas import ItemSchema, ItemUpdateSchema
+
+blp = Blueprint("Items", "items", description="Operations on items")
+
+
+@blp.route("/items/")
+class Item(MethodView):
+ @blp.response(200, ItemSchema)
+ def get(self, item_id):
+ item = ItemModel.query.get_or_404(item_id)
+ return item
+
+ def delete(self, item_id):
+ item = ItemModel.query.get_or_404(item_id)
+ db.session.delete(item)
+ db.session.commit()
+ return {"message": "Item deleted."}
+
+ @blp.arguments(ItemUpdateSchema)
+ @blp.response(200, ItemSchema)
+ def put(self, item_data, item_id):
+ item = ItemModel.query.get_or_404(item_id)
+
+ if item:
+ item.price = item_data["price"]
+ item.name = item_data["name"]
+ else:
+ item = ItemModel(**item_data)
+
+ db.session.add(item)
+ db.session.commit()
+
+ return item
+
+
+@blp.route("/items")
+class ItemList(MethodView):
+ @blp.response(200, ItemSchema(many=True))
+ def get(self):
+ return ItemModel.query.all()
+
+ @blp.arguments(ItemSchema)
+ @blp.response(201, ItemSchema)
+ def post(self, item_data):
+ item = ItemModel(**item_data)
+
+ try:
+ db.session.add(item)
+ db.session.commit()
+ except SQLAlchemyError:
+ abort(500, message="An error occurred while inserting the item.")
+
+ return item
diff --git a/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/end/resources/store.py b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/end/resources/store.py
new file mode 100644
index 00000000..c04c6dc3
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/end/resources/store.py
@@ -0,0 +1,48 @@
+from flask.views import MethodView
+from flask_smorest import Blueprint, abort
+from sqlalchemy.exc import SQLAlchemyError, IntegrityError
+
+from db import db
+from models import StoreModel
+from schemas import StoreSchema
+
+
+blp = Blueprint("Stores", "stores", description="Operations on stores")
+
+
+@blp.route("/stores/")
+class Store(MethodView):
+ @blp.response(200, StoreSchema)
+ def get(self, store_id):
+ store = StoreModel.query.get_or_404(store_id)
+ return store
+
+ def delete(self, store_id):
+ store = StoreModel.query.get_or_404(store_id)
+ db.session.delete(store)
+ db.session.commit()
+ return {"message": "Store deleted"}, 200
+
+
+@blp.route("/stores")
+class StoreList(MethodView):
+ @blp.response(200, StoreSchema(many=True))
+ def get(self):
+ return StoreModel.query.all()
+
+ @blp.arguments(StoreSchema)
+ @blp.response(201, StoreSchema)
+ def post(self, store_data):
+ store = StoreModel(**store_data)
+ try:
+ db.session.add(store)
+ db.session.commit()
+ except IntegrityError:
+ abort(
+ 400,
+ message="A store with that name already exists.",
+ )
+ except SQLAlchemyError:
+ abort(500, message="An error occurred creating the store.")
+
+ return store
diff --git a/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/end/schemas.py b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/end/schemas.py
new file mode 100644
index 00000000..2619c4ee
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/end/schemas.py
@@ -0,0 +1,26 @@
+from marshmallow import Schema, fields
+
+
+class PlainItemSchema(Schema):
+ id = fields.Int(dump_only=True)
+ name = fields.Str(required=True)
+ price = fields.Float(required=True)
+
+
+class PlainStoreSchema(Schema):
+ id = fields.Int(dump_only=True)
+ name = fields.Str()
+
+
+class ItemSchema(PlainItemSchema):
+ store_id = fields.Int(required=True, load_only=True)
+ store = fields.Nested(lambda: PlainStoreSchema(), dump_only=True)
+
+
+class ItemUpdateSchema(Schema):
+ name = fields.Str()
+ price = fields.Float()
+
+
+class StoreSchema(PlainStoreSchema):
+ items = fields.List(fields.Nested(PlainItemSchema()), dump_only=True)
diff --git a/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/start/.flaskenv b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/start/.flaskenv
new file mode 100644
index 00000000..75473901
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/start/.flaskenv
@@ -0,0 +1,2 @@
+FLASK_APP=app
+FLASK_ENV=development
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/start/Dockerfile b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/start/Dockerfile
new file mode 100644
index 00000000..652afba1
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/start/Dockerfile
@@ -0,0 +1,7 @@
+FROM python:3.10
+EXPOSE 5000
+WORKDIR /app
+COPY ./requirements.txt requirements.txt
+RUN pip install --no-cache-dir --upgrade -r requirements.txt
+COPY . .
+CMD ["flask", "run", "--host", "0.0.0.0"]
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/start/app.py b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/start/app.py
new file mode 100644
index 00000000..f3f475ca
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/start/app.py
@@ -0,0 +1,35 @@
+from flask import Flask
+from flask_smorest import Api
+
+from db import db
+
+import models
+
+from resources.item import blp as ItemBlueprint
+from resources.store import blp as StoreBlueprint
+
+
+def create_app(db_url=None):
+ app = Flask(__name__)
+ app.config["API_TITLE"] = "Stores REST API"
+ app.config["API_VERSION"] = "v1"
+ app.config["OPENAPI_VERSION"] = "3.0.3"
+ app.config["OPENAPI_URL_PREFIX"] = "/"
+ app.config["OPENAPI_SWAGGER_UI_PATH"] = "/swagger-ui"
+ app.config[
+ "OPENAPI_SWAGGER_UI_URL"
+ ] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist/"
+ app.config["SQLALCHEMY_DATABASE_URI"] = db_url or "sqlite:///data.db"
+ app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
+ app.config["PROPAGATE_EXCEPTIONS"] = True
+ db.init_app(app)
+ api = Api(app)
+
+ @app.before_first_request
+ def create_tables():
+ db.create_all()
+
+ api.register_blueprint(ItemBlueprint)
+ api.register_blueprint(StoreBlueprint)
+
+ return app
diff --git a/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/start/db.py b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/start/db.py
new file mode 100644
index 00000000..f0b13d6f
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/start/db.py
@@ -0,0 +1,3 @@
+from flask_sqlalchemy import SQLAlchemy
+
+db = SQLAlchemy()
diff --git a/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/start/models/__init__.py b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/start/models/__init__.py
new file mode 100644
index 00000000..b57f3f8a
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/start/models/__init__.py
@@ -0,0 +1,5 @@
+from models.user import UserModel
+from models.item import ItemModel
+from models.tag import TagModel
+from models.store import StoreModel
+from models.item_tags import ItemsTags
diff --git a/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/start/models/item.py b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/start/models/item.py
new file mode 100644
index 00000000..74797e0e
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/start/models/item.py
@@ -0,0 +1,14 @@
+from db import db
+
+
+class ItemModel(db.Model):
+ __tablename__ = "items"
+
+ id = db.Column(db.Integer, primary_key=True)
+ name = db.Column(db.String(80), unique=True, nullable=False)
+ price = db.Column(db.Float(precision=2), unique=False, nullable=False)
+
+ store_id = db.Column(
+ db.Integer, db.ForeignKey("stores.id"), unique=False, nullable=False
+ )
+ store = db.relationship("StoreModel", back_populates="items")
diff --git a/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/start/models/store.py b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/start/models/store.py
new file mode 100644
index 00000000..699147f9
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/start/models/store.py
@@ -0,0 +1,10 @@
+from db import db
+
+
+class StoreModel(db.Model):
+ __tablename__ = "stores"
+
+ id = db.Column(db.Integer, primary_key=True)
+ name = db.Column(db.String(80), unique=True, nullable=False)
+
+ items = db.relationship("ItemModel", back_populates="store", lazy="dynamic")
diff --git a/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/start/requirements.txt b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/start/requirements.txt
new file mode 100644
index 00000000..4764bf34
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/start/requirements.txt
@@ -0,0 +1,4 @@
+flask
+flask-smorest
+python-dotenv
+marshmallow
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/start/resources/__init__.py b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/start/resources/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/start/resources/item.py b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/start/resources/item.py
new file mode 100644
index 00000000..e7b7f053
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/start/resources/item.py
@@ -0,0 +1,57 @@
+from flask.views import MethodView
+from flask_smorest import Blueprint, abort
+from sqlalchemy.exc import SQLAlchemyError
+
+from db import db
+from models import ItemModel
+from schemas import ItemSchema, ItemUpdateSchema
+
+blp = Blueprint("Items", "items", description="Operations on items")
+
+
+@blp.route("/items/")
+class Item(MethodView):
+ @blp.response(200, ItemSchema)
+ def get(self, item_id):
+ item = ItemModel.query.get_or_404(item_id)
+ return item
+
+ def delete(self, item_id):
+ item = ItemModel.query.get_or_404(item_id)
+ raise NotImplementedError("Deleting an item is not implemented.")
+
+ @blp.arguments(ItemUpdateSchema)
+ @blp.response(200, ItemSchema)
+ def put(self, item_data, item_id):
+ item = ItemModel.query.get_or_404(item_id)
+
+ if item:
+ item.price = item_data["price"]
+ item.name = item_data["name"]
+ else:
+ item = ItemModel(**item_data)
+
+ db.session.add(item)
+ db.session.commit()
+
+ return item
+
+
+@blp.route("/items")
+class ItemList(MethodView):
+ @blp.response(200, ItemSchema(many=True))
+ def get(self):
+ return ItemModel.query.all()
+
+ @blp.arguments(ItemSchema)
+ @blp.response(201, ItemSchema)
+ def post(self, item_data):
+ item = ItemModel(**item_data)
+
+ try:
+ db.session.add(item)
+ db.session.commit()
+ except SQLAlchemyError:
+ abort(500, message="An error occurred while inserting the item.")
+
+ return item
diff --git a/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/start/resources/store.py b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/start/resources/store.py
new file mode 100644
index 00000000..0cd67a95
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/start/resources/store.py
@@ -0,0 +1,46 @@
+from flask.views import MethodView
+from flask_smorest import Blueprint, abort
+from sqlalchemy.exc import SQLAlchemyError, IntegrityError
+
+from db import db
+from models import StoreModel
+from schemas import StoreSchema
+
+
+blp = Blueprint("Stores", "stores", description="Operations on stores")
+
+
+@blp.route("/stores/")
+class Store(MethodView):
+ @blp.response(200, StoreSchema)
+ def get(self, store_id):
+ store = StoreModel.query.get_or_404(store_id)
+ return store
+
+ def delete(self, store_id):
+ store = StoreModel.query.get_or_404(store_id)
+ raise NotImplementedError("Deleting a store is not implemented.")
+
+
+@blp.route("/stores")
+class StoreList(MethodView):
+ @blp.response(200, StoreSchema(many=True))
+ def get(self):
+ return StoreModel.query.all()
+
+ @blp.arguments(StoreSchema)
+ @blp.response(201, StoreSchema)
+ def post(self, store_data):
+ store = StoreModel(**store_data)
+ try:
+ db.session.add(store)
+ db.session.commit()
+ except IntegrityError:
+ abort(
+ 400,
+ message="A store with that name already exists.",
+ )
+ except SQLAlchemyError:
+ abort(500, message="An error occurred creating the store.")
+
+ return store
diff --git a/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/start/schemas.py b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/start/schemas.py
new file mode 100644
index 00000000..2619c4ee
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/09_delete_models_sqlalchemy/start/schemas.py
@@ -0,0 +1,26 @@
+from marshmallow import Schema, fields
+
+
+class PlainItemSchema(Schema):
+ id = fields.Int(dump_only=True)
+ name = fields.Str(required=True)
+ price = fields.Float(required=True)
+
+
+class PlainStoreSchema(Schema):
+ id = fields.Int(dump_only=True)
+ name = fields.Str()
+
+
+class ItemSchema(PlainItemSchema):
+ store_id = fields.Int(required=True, load_only=True)
+ store = fields.Nested(lambda: PlainStoreSchema(), dump_only=True)
+
+
+class ItemUpdateSchema(Schema):
+ name = fields.Str()
+ price = fields.Float()
+
+
+class StoreSchema(PlainStoreSchema):
+ items = fields.List(fields.Nested(PlainItemSchema()), dump_only=True)
diff --git a/docs/docs/06_sql_storage_sqlalchemy/10_conclusion/README.md b/docs/docs/06_sql_storage_sqlalchemy/10_conclusion/README.md
new file mode 100644
index 00000000..6b94d8bc
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/10_conclusion/README.md
@@ -0,0 +1,103 @@
+---
+title: Conclusion of this section
+description: Review everything we've changed this section to add SQL storage with SQLAlchemy to our API.
+---
+
+# Conclusion of this section
+
+Adding SQL storage to our app has required quite a few changes! Let's do a quick review.
+
+## Installed SQLAlchemy and Flask-SQLAlchemy
+
+```
+pip install sqlalchemy flask-sqlalchemy
+```
+
+And
+
+```text title="requirements.txt"
+sqlalchemy
+flask-sqlalchemy
+```
+
+## Created models
+
+```python title="models/item.py"
+from db import db
+
+
+class ItemModel(db.Model):
+ __tablename__ = "items"
+
+ id = db.Column(db.Integer, primary_key=True)
+ name = db.Column(db.String(80), unique=True, nullable=False)
+ price = db.Column(db.Float(precision=2), unique=False, nullable=False)
+
+ store_id = db.Column(
+ db.Integer, db.ForeignKey("stores.id"), unique=False, nullable=False
+ )
+ store = db.relationship("StoreModel", back_populates="items")
+```
+
+And
+
+```python title="models/store.py"
+from db import db
+
+
+class StoreModel(db.Model):
+ __tablename__ = "stores"
+
+ id = db.Column(db.Integer, primary_key=True)
+ name = db.Column(db.String(80), unique=True, nullable=False)
+
+ items = db.relationship("ItemModel", back_populates="store", lazy="dynamic")
+```
+
+## Updated resources to use SQLAlchemy
+
+Previously we were using Python dictionaries as a database. Now we've swapped them out for using SQLAlchemy models by:
+
+- Importing the models in our resource files
+- Retrieving models from the database with `ModelClass.query.get_or_404(model_id)`.
+- Updating models by changing attributes, or creating new model class instances, and then saving and committing with `db.session.add(model_instance)` and `db.session.commit()`.
+- Deleting models with `db.session.delete(model_instance)` followed by `db.session.commit()`.
+
+## Updated marshmallow schemas
+
+Since now our models have relationships, that means that the schemas can have `Nested` fields.
+
+The schemas that don't have `Nested` fields we've called "Plain" schemas, and those that do are named after the model they represent.
+
+```python title="schemas.py"
+from marshmallow import Schema, fields
+
+
+class PlainItemSchema(Schema):
+ id = fields.Int(dump_only=True)
+ name = fields.Str(required=True)
+ price = fields.Float(required=True)
+
+
+class PlainStoreSchema(Schema):
+ id = fields.Int(dump_only=True)
+ name = fields.Str()
+
+
+class ItemSchema(PlainItemSchema):
+ store_id = fields.Int(required=True, load_only=True)
+ store = fields.Nested(PlainStoreSchema(), dump_only=True)
+
+
+class ItemUpdateSchema(Schema):
+ name = fields.Str()
+ price = fields.Float()
+
+
+class StoreSchema(PlainStoreSchema):
+ items = fields.List(fields.Nested(PlainItemSchema()), dump_only=True)
+```
+
+And that's it! Quite a few changes, but hopefully you're still with me.
+
+In the following sections we'll be adding more functionality to our API, so stay tuned!
\ No newline at end of file
diff --git a/docs/docs/06_sql_storage_sqlalchemy/_category_.json b/docs/docs/06_sql_storage_sqlalchemy/_category_.json
new file mode 100644
index 00000000..fd8d269b
--- /dev/null
+++ b/docs/docs/06_sql_storage_sqlalchemy/_category_.json
@@ -0,0 +1,4 @@
+{
+ "label": "SQL Storage with Flask-SQLAlchemy",
+ "position": 6
+}
diff --git a/project/03-items-stores-smorest/app.py b/project/03-items-stores-smorest/app.py
index 7057517e..5afd6e7b 100644
--- a/project/03-items-stores-smorest/app.py
+++ b/project/03-items-stores-smorest/app.py
@@ -10,7 +10,7 @@
app.config["PROPAGATE_EXCEPTIONS"] = True
app.config["API_TITLE"] = "Stores REST API"
app.config["API_VERSION"] = "v1"
-app.config["OPENAPI_VERSION"] = "3.0.2"
+app.config["OPENAPI_VERSION"] = "3.0.3"
app.config["OPENAPI_URL_PREFIX"] = "/"
app.config["OPENAPI_SWAGGER_UI_PATH"] = "/swagger-ui"
app.config["OPENAPI_SWAGGER_UI_URL"] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist/"
diff --git a/project/03-items-stores-smorest/schemas.py b/project/03-items-stores-smorest/schemas.py
index 8ebdc231..0a4ff8d4 100644
--- a/project/03-items-stores-smorest/schemas.py
+++ b/project/03-items-stores-smorest/schemas.py
@@ -8,12 +8,6 @@ class ItemSchema(Schema):
store_id = fields.Int(required=True)
-class ItemWithoutStoreSchema(Schema):
- id = fields.Str(dump_only=True)
- name = fields.Str(required=True)
- price = fields.Float(required=True)
-
-
class ItemUpdateSchema(Schema):
name = fields.Str()
price = fields.Float()
@@ -22,8 +16,3 @@ class ItemUpdateSchema(Schema):
class StoreSchema(Schema):
id = fields.Str(dump_only=True)
name = fields.Str(required=True)
-
-
-class StoreWitoutItemsSchema(Schema):
- id = fields.Str()
- name = fields.Str()
diff --git a/project/04-items-stores-smorest-sqlalchemy/.flaskenv b/project/04-items-stores-smorest-sqlalchemy/.flaskenv
new file mode 100644
index 00000000..75473901
--- /dev/null
+++ b/project/04-items-stores-smorest-sqlalchemy/.flaskenv
@@ -0,0 +1,2 @@
+FLASK_APP=app
+FLASK_ENV=development
\ No newline at end of file
diff --git a/project/04-items-stores-smorest-sqlalchemy/Dockerfile b/project/04-items-stores-smorest-sqlalchemy/Dockerfile
new file mode 100644
index 00000000..652afba1
--- /dev/null
+++ b/project/04-items-stores-smorest-sqlalchemy/Dockerfile
@@ -0,0 +1,7 @@
+FROM python:3.10
+EXPOSE 5000
+WORKDIR /app
+COPY ./requirements.txt requirements.txt
+RUN pip install --no-cache-dir --upgrade -r requirements.txt
+COPY . .
+CMD ["flask", "run", "--host", "0.0.0.0"]
\ No newline at end of file
diff --git a/project/04-items-stores-smorest-sqlalchemy/app.py b/project/04-items-stores-smorest-sqlalchemy/app.py
new file mode 100644
index 00000000..f3f475ca
--- /dev/null
+++ b/project/04-items-stores-smorest-sqlalchemy/app.py
@@ -0,0 +1,35 @@
+from flask import Flask
+from flask_smorest import Api
+
+from db import db
+
+import models
+
+from resources.item import blp as ItemBlueprint
+from resources.store import blp as StoreBlueprint
+
+
+def create_app(db_url=None):
+ app = Flask(__name__)
+ app.config["API_TITLE"] = "Stores REST API"
+ app.config["API_VERSION"] = "v1"
+ app.config["OPENAPI_VERSION"] = "3.0.3"
+ app.config["OPENAPI_URL_PREFIX"] = "/"
+ app.config["OPENAPI_SWAGGER_UI_PATH"] = "/swagger-ui"
+ app.config[
+ "OPENAPI_SWAGGER_UI_URL"
+ ] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist/"
+ app.config["SQLALCHEMY_DATABASE_URI"] = db_url or "sqlite:///data.db"
+ app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
+ app.config["PROPAGATE_EXCEPTIONS"] = True
+ db.init_app(app)
+ api = Api(app)
+
+ @app.before_first_request
+ def create_tables():
+ db.create_all()
+
+ api.register_blueprint(ItemBlueprint)
+ api.register_blueprint(StoreBlueprint)
+
+ return app
diff --git a/project/04-items-stores-smorest-sqlalchemy/db.py b/project/04-items-stores-smorest-sqlalchemy/db.py
new file mode 100644
index 00000000..f0b13d6f
--- /dev/null
+++ b/project/04-items-stores-smorest-sqlalchemy/db.py
@@ -0,0 +1,3 @@
+from flask_sqlalchemy import SQLAlchemy
+
+db = SQLAlchemy()
diff --git a/project/04-items-stores-smorest-sqlalchemy/models/__init__.py b/project/04-items-stores-smorest-sqlalchemy/models/__init__.py
new file mode 100644
index 00000000..7cab8b1b
--- /dev/null
+++ b/project/04-items-stores-smorest-sqlalchemy/models/__init__.py
@@ -0,0 +1,2 @@
+from models.item import ItemModel
+from models.store import StoreModel
diff --git a/project/04-items-stores-smorest-sqlalchemy/models/item.py b/project/04-items-stores-smorest-sqlalchemy/models/item.py
new file mode 100644
index 00000000..74797e0e
--- /dev/null
+++ b/project/04-items-stores-smorest-sqlalchemy/models/item.py
@@ -0,0 +1,14 @@
+from db import db
+
+
+class ItemModel(db.Model):
+ __tablename__ = "items"
+
+ id = db.Column(db.Integer, primary_key=True)
+ name = db.Column(db.String(80), unique=True, nullable=False)
+ price = db.Column(db.Float(precision=2), unique=False, nullable=False)
+
+ store_id = db.Column(
+ db.Integer, db.ForeignKey("stores.id"), unique=False, nullable=False
+ )
+ store = db.relationship("StoreModel", back_populates="items")
diff --git a/project/04-items-stores-smorest-sqlalchemy/models/store.py b/project/04-items-stores-smorest-sqlalchemy/models/store.py
new file mode 100644
index 00000000..699147f9
--- /dev/null
+++ b/project/04-items-stores-smorest-sqlalchemy/models/store.py
@@ -0,0 +1,10 @@
+from db import db
+
+
+class StoreModel(db.Model):
+ __tablename__ = "stores"
+
+ id = db.Column(db.Integer, primary_key=True)
+ name = db.Column(db.String(80), unique=True, nullable=False)
+
+ items = db.relationship("ItemModel", back_populates="store", lazy="dynamic")
diff --git a/project/04-items-stores-smorest-sqlalchemy/requirements.txt b/project/04-items-stores-smorest-sqlalchemy/requirements.txt
new file mode 100644
index 00000000..32a85f28
--- /dev/null
+++ b/project/04-items-stores-smorest-sqlalchemy/requirements.txt
@@ -0,0 +1,6 @@
+flask
+flask-smorest
+python-dotenv
+marshmallow
+sqlalchemy
+flask-sqlalchemy
\ No newline at end of file
diff --git a/project/04-items-stores-smorest-sqlalchemy/resources/__init__.py b/project/04-items-stores-smorest-sqlalchemy/resources/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/project/04-items-stores-smorest-sqlalchemy/resources/item.py b/project/04-items-stores-smorest-sqlalchemy/resources/item.py
new file mode 100644
index 00000000..35190b12
--- /dev/null
+++ b/project/04-items-stores-smorest-sqlalchemy/resources/item.py
@@ -0,0 +1,59 @@
+from flask.views import MethodView
+from flask_smorest import Blueprint, abort
+from sqlalchemy.exc import SQLAlchemyError
+
+from db import db
+from models import ItemModel
+from schemas import ItemSchema, ItemUpdateSchema
+
+blp = Blueprint("Items", "items", description="Operations on items")
+
+
+@blp.route("/items/")
+class Item(MethodView):
+ @blp.response(200, ItemSchema)
+ def get(self, item_id):
+ item = ItemModel.query.get_or_404(item_id)
+ return item
+
+ def delete(self, item_id):
+ item = ItemModel.query.get_or_404(item_id)
+ db.session.delete(item)
+ db.session.commit()
+ return {"message": "Item deleted."}
+
+ @blp.arguments(ItemUpdateSchema)
+ @blp.response(200, ItemSchema)
+ def put(self, item_data, item_id):
+ item = ItemModel.query.get_or_404(item_id)
+
+ if item:
+ item.price = item_data["price"]
+ item.name = item_data["name"]
+ else:
+ item = ItemModel(**item_data)
+
+ db.session.add(item)
+ db.session.commit()
+
+ return item
+
+
+@blp.route("/items")
+class ItemList(MethodView):
+ @blp.response(200, ItemSchema(many=True))
+ def get(self):
+ return ItemModel.query.all()
+
+ @blp.arguments(ItemSchema)
+ @blp.response(201, ItemSchema)
+ def post(self, item_data):
+ item = ItemModel(**item_data)
+
+ try:
+ db.session.add(item)
+ db.session.commit()
+ except SQLAlchemyError:
+ abort(500, message="An error occurred while inserting the item.")
+
+ return item
diff --git a/project/04-items-stores-smorest-sqlalchemy/resources/store.py b/project/04-items-stores-smorest-sqlalchemy/resources/store.py
new file mode 100644
index 00000000..c04c6dc3
--- /dev/null
+++ b/project/04-items-stores-smorest-sqlalchemy/resources/store.py
@@ -0,0 +1,48 @@
+from flask.views import MethodView
+from flask_smorest import Blueprint, abort
+from sqlalchemy.exc import SQLAlchemyError, IntegrityError
+
+from db import db
+from models import StoreModel
+from schemas import StoreSchema
+
+
+blp = Blueprint("Stores", "stores", description="Operations on stores")
+
+
+@blp.route("/stores/")
+class Store(MethodView):
+ @blp.response(200, StoreSchema)
+ def get(self, store_id):
+ store = StoreModel.query.get_or_404(store_id)
+ return store
+
+ def delete(self, store_id):
+ store = StoreModel.query.get_or_404(store_id)
+ db.session.delete(store)
+ db.session.commit()
+ return {"message": "Store deleted"}, 200
+
+
+@blp.route("/stores")
+class StoreList(MethodView):
+ @blp.response(200, StoreSchema(many=True))
+ def get(self):
+ return StoreModel.query.all()
+
+ @blp.arguments(StoreSchema)
+ @blp.response(201, StoreSchema)
+ def post(self, store_data):
+ store = StoreModel(**store_data)
+ try:
+ db.session.add(store)
+ db.session.commit()
+ except IntegrityError:
+ abort(
+ 400,
+ message="A store with that name already exists.",
+ )
+ except SQLAlchemyError:
+ abort(500, message="An error occurred creating the store.")
+
+ return store
diff --git a/project/04-items-stores-smorest-sqlalchemy/schemas.py b/project/04-items-stores-smorest-sqlalchemy/schemas.py
new file mode 100644
index 00000000..2619c4ee
--- /dev/null
+++ b/project/04-items-stores-smorest-sqlalchemy/schemas.py
@@ -0,0 +1,26 @@
+from marshmallow import Schema, fields
+
+
+class PlainItemSchema(Schema):
+ id = fields.Int(dump_only=True)
+ name = fields.Str(required=True)
+ price = fields.Float(required=True)
+
+
+class PlainStoreSchema(Schema):
+ id = fields.Int(dump_only=True)
+ name = fields.Str()
+
+
+class ItemSchema(PlainItemSchema):
+ store_id = fields.Int(required=True, load_only=True)
+ store = fields.Nested(lambda: PlainStoreSchema(), dump_only=True)
+
+
+class ItemUpdateSchema(Schema):
+ name = fields.Str()
+ price = fields.Float()
+
+
+class StoreSchema(PlainStoreSchema):
+ items = fields.List(fields.Nested(PlainItemSchema()), dump_only=True)
diff --git a/project/using-flask-smorest-docker b/project/using-flask-smorest-docker
index cbe85ada..26db6beb 160000
--- a/project/using-flask-smorest-docker
+++ b/project/using-flask-smorest-docker
@@ -1 +1 @@
-Subproject commit cbe85adae28943299d4f83ed1396cf9094b980dd
+Subproject commit 26db6beb306a2552bf0505871f9b71cca66e4558
diff --git a/project/using-flask-smorest/app.py b/project/using-flask-smorest/app.py
index 5c2e518e..a6b06786 100644
--- a/project/using-flask-smorest/app.py
+++ b/project/using-flask-smorest/app.py
@@ -14,7 +14,7 @@
app = Flask(__name__)
app.config["API_TITLE"] = "Stores REST API"
app.config["API_VERSION"] = "v1"
-app.config["OPENAPI_VERSION"] = "3.0.2"
+app.config["OPENAPI_VERSION"] = "3.0.3"
app.config["OPENAPI_URL_PREFIX"] = "/"
app.config["OPENAPI_SWAGGER_UI_PATH"] = "/swagger-ui"
app.config["OPENAPI_SWAGGER_UI_URL"] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist/"