Skip to content

Commit

Permalink
Merge pull request #50 from tecladocode/jose/cou-95-write-flask-sqlal…
Browse files Browse the repository at this point in the history
…chemy-section
  • Loading branch information
jslvtr authored Jun 1, 2022
2 parents 6669065 + 5a7c1f2 commit 0b98f3b
Show file tree
Hide file tree
Showing 247 changed files with 3,823 additions and 42 deletions.
4 changes: 0 additions & 4 deletions docs/docs-upcoming/06_sql_storage/_category_.json

This file was deleted.

File renamed without changes.
4 changes: 0 additions & 4 deletions docs/docs-upcoming/07_flask_sqlalchemy/_category_.json

This file was deleted.

File renamed without changes.

This file was deleted.

1 change: 0 additions & 1 deletion docs/docs-upcoming/11_deploy_to_render/README.md

This file was deleted.

This file was deleted.

1 change: 0 additions & 1 deletion docs/docs-upcoming/12_celery_background_tasks/README.md

This file was deleted.

7 changes: 0 additions & 7 deletions docs/docs/03_first_rest_api/README.md

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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/"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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/"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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/"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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/"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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/"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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/"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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/"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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/"
Expand Down
1 change: 0 additions & 1 deletion docs/docs/05_flask_smorest/README.md

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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
```

<details>
<summary>What is Flask-SQLAlchemy?</summary>
<div>
<p>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.</p>
<p>Flask-SQLAlchemy is a Flask extension which helps connect SQLAlchemy to Flask apps.</p>
</div>
</details>

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.
Original file line number Diff line number Diff line change
@@ -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)
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FLASK_APP=app
FLASK_ENV=development
Original file line number Diff line number Diff line change
@@ -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"]
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
flask
flask-smorest
python-dotenv
marshmallow
Original file line number Diff line number Diff line change
@@ -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/<string:item_id>")
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.")
Original file line number Diff line number Diff line change
@@ -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/<string:store_id>")
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.")
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FLASK_APP=app
FLASK_ENV=development
Original file line number Diff line number Diff line change
@@ -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"]
Original file line number Diff line number Diff line change
@@ -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)
Loading

1 comment on commit 0b98f3b

@vercel
Copy link

@vercel vercel bot commented on 0b98f3b Jun 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.