diff --git a/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_models_sqlalchemy/README.md b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_models_sqlalchemy/README.md new file mode 100644 index 00000000..194606c1 --- /dev/null +++ b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_models_sqlalchemy/README.md @@ -0,0 +1,41 @@ +--- +title: Delete models with relationships +description: Tell SQLAlchemy what to do with related models when you delete the parent. +--- + +# Delete models with relationships using cascades + +When you delete a model that has a relationship to other models that still exist, the default behavior in SQLAlchemy with PostgreSQL is to raise an error. This is because SQLAlchemy does not want to allow you to accidentally delete data that is still being used by other models. + +Let's say you have a `Store 1` that has two items, `Item 1` and `Item 2`. If you try to delete Store 1 without first deleting Item 1 and Item 2, SQLAlchemy will raise an error because the items are still related to the store. + +This means the items have a **Foreign Key** that references the store you're trying to delete. If the store actually was deleted, the items have a store ID that references something that doesn't exist. + +To fix this, you can use a feature called "cascading deletes". Cascading deletes allow you to specify that when a model is deleted, any related models should also be deleted automatically. + +SQLAlchemy makes it easy to add cascades to our models, here's how you might do that! + +```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", cascade="all, delete") + # highlight-end +``` + +Remember that `StoreModel` and `ItemModel` have a one-to-many relationship, where each store can have multiple items, and each item belongs to a single store. + +The `cascade="all,delete"` argument in the `relationship()` call for the `StoreModel.items` attribute specifies that when a store is deleted, all of its related items should also be deleted. + +If you add a `cascade` on the relationship in the `ItemModel`, then when an item is deleted, its related store should also be deleted. This is not what we want, so we won't add a cascade to `ItemModel`. + +With this code in place, if you try to delete a store that still has items, the items will be deleted automatically along with the store. This will allow you to delete the store without having to delete the items individually. + +For more information, I strongly recommend reading [the official documentation](https://docs.sqlalchemy.org/en/20/orm/cascades.html#delete)! There are also other cascade options you can pass in depending on what you want to happen to related models when the parent changes or is deleted. \ No newline at end of file diff --git a/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_models_sqlalchemy/end/.flaskenv b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_models_sqlalchemy/end/.flaskenv new file mode 100644 index 00000000..ccb3106e --- /dev/null +++ b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_models_sqlalchemy/end/.flaskenv @@ -0,0 +1,2 @@ +FLASK_APP=app +FLASK_DEBUG=True \ No newline at end of file diff --git a/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_models_sqlalchemy/end/Dockerfile b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_models_sqlalchemy/end/Dockerfile new file mode 100644 index 00000000..652afba1 --- /dev/null +++ b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_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/10_delete_related_models_sqlalchemy/end/app.py b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_models_sqlalchemy/end/app.py new file mode 100644 index 00000000..539afd91 --- /dev/null +++ b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_models_sqlalchemy/end/app.py @@ -0,0 +1,34 @@ +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) + + with app.app_context(): + db.create_all() + + api.register_blueprint(ItemBlueprint) + api.register_blueprint(StoreBlueprint) + + return app diff --git a/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_models_sqlalchemy/end/db.py b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_models_sqlalchemy/end/db.py new file mode 100644 index 00000000..f0b13d6f --- /dev/null +++ b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_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/10_delete_related_models_sqlalchemy/end/models/__init__.py b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_models_sqlalchemy/end/models/__init__.py new file mode 100644 index 00000000..7cab8b1b --- /dev/null +++ b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_models_sqlalchemy/end/models/__init__.py @@ -0,0 +1,2 @@ +from models.item import ItemModel +from models.store import StoreModel diff --git a/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_models_sqlalchemy/end/models/item.py b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_models_sqlalchemy/end/models/item.py new file mode 100644 index 00000000..56cb307d --- /dev/null +++ b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_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=False, 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/10_delete_related_models_sqlalchemy/end/models/store.py b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_models_sqlalchemy/end/models/store.py new file mode 100644 index 00000000..699147f9 --- /dev/null +++ b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_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/10_delete_related_models_sqlalchemy/end/requirements.txt b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_models_sqlalchemy/end/requirements.txt new file mode 100644 index 00000000..77fe98e4 --- /dev/null +++ b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_models_sqlalchemy/end/requirements.txt @@ -0,0 +1,5 @@ +flask +flask-smorest +flask-sqlalchemy +python-dotenv +marshmallow \ No newline at end of file diff --git a/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_models_sqlalchemy/end/resources/__init__.py b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_models_sqlalchemy/end/resources/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_models_sqlalchemy/end/resources/item.py b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_models_sqlalchemy/end/resources/item.py new file mode 100644 index 00000000..c8a96721 --- /dev/null +++ b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_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("/item/") +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(item_id) + + if item: + item.price = item_data["price"] + item.name = item_data["name"] + else: + item = ItemModel(id=item_id, **item_data) + + db.session.add(item) + db.session.commit() + + return item + + +@blp.route("/item") +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/10_delete_related_models_sqlalchemy/end/resources/store.py b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_models_sqlalchemy/end/resources/store.py new file mode 100644 index 00000000..06bc0e24 --- /dev/null +++ b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_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("/store/") +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("/store") +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/10_delete_related_models_sqlalchemy/end/schemas.py b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_models_sqlalchemy/end/schemas.py new file mode 100644 index 00000000..fbdcf3de --- /dev/null +++ b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_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(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_delete_related_models_sqlalchemy/start/.flaskenv b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_models_sqlalchemy/start/.flaskenv new file mode 100644 index 00000000..ccb3106e --- /dev/null +++ b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_models_sqlalchemy/start/.flaskenv @@ -0,0 +1,2 @@ +FLASK_APP=app +FLASK_DEBUG=True \ No newline at end of file diff --git a/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_models_sqlalchemy/start/Dockerfile b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_models_sqlalchemy/start/Dockerfile new file mode 100644 index 00000000..652afba1 --- /dev/null +++ b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_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/10_delete_related_models_sqlalchemy/start/app.py b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_models_sqlalchemy/start/app.py new file mode 100644 index 00000000..539afd91 --- /dev/null +++ b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_models_sqlalchemy/start/app.py @@ -0,0 +1,34 @@ +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) + + with app.app_context(): + db.create_all() + + api.register_blueprint(ItemBlueprint) + api.register_blueprint(StoreBlueprint) + + return app diff --git a/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_models_sqlalchemy/start/db.py b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_models_sqlalchemy/start/db.py new file mode 100644 index 00000000..f0b13d6f --- /dev/null +++ b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_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/10_delete_related_models_sqlalchemy/start/models/__init__.py b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_models_sqlalchemy/start/models/__init__.py new file mode 100644 index 00000000..b57f3f8a --- /dev/null +++ b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_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/10_delete_related_models_sqlalchemy/start/models/item.py b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_models_sqlalchemy/start/models/item.py new file mode 100644 index 00000000..56cb307d --- /dev/null +++ b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_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=False, 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/10_delete_related_models_sqlalchemy/start/models/store.py b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_models_sqlalchemy/start/models/store.py new file mode 100644 index 00000000..699147f9 --- /dev/null +++ b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_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/10_delete_related_models_sqlalchemy/start/requirements.txt b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_models_sqlalchemy/start/requirements.txt new file mode 100644 index 00000000..4764bf34 --- /dev/null +++ b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_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/10_delete_related_models_sqlalchemy/start/resources/__init__.py b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_models_sqlalchemy/start/resources/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_models_sqlalchemy/start/resources/item.py b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_models_sqlalchemy/start/resources/item.py new file mode 100644 index 00000000..71b6f62c --- /dev/null +++ b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_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("/item/") +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(item_id) + + if item: + item.price = item_data["price"] + item.name = item_data["name"] + else: + item = ItemModel(id=item_id, **item_data) + + db.session.add(item) + db.session.commit() + + return item + + +@blp.route("/item") +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/10_delete_related_models_sqlalchemy/start/resources/store.py b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_models_sqlalchemy/start/resources/store.py new file mode 100644 index 00000000..f2d77a29 --- /dev/null +++ b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_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("/store/") +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("/store") +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/10_delete_related_models_sqlalchemy/start/schemas.py b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_models_sqlalchemy/start/schemas.py new file mode 100644 index 00000000..fbdcf3de --- /dev/null +++ b/docs/docs/06_sql_storage_sqlalchemy/10_delete_related_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(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/11_conclusion/README.md similarity index 100% rename from docs/docs/06_sql_storage_sqlalchemy/10_conclusion/README.md rename to docs/docs/06_sql_storage_sqlalchemy/11_conclusion/README.md