Skip to content

Commit 8593436

Browse files
committed
add examples/complete_db
1 parent 422972c commit 8593436

File tree

12 files changed

+358
-0
lines changed

12 files changed

+358
-0
lines changed

examples/complete_db/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .app import create_app

examples/complete_db/app.py

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from complete_db.ext import af, db, ma
2+
from flask import Flask
3+
from werkzeug.utils import import_string
4+
5+
6+
def create_app(conf=None):
7+
app = Flask(__name__)
8+
9+
if not conf:
10+
conf = "base"
11+
12+
config = import_string(f"complete_db.conf.{conf.lower()}")()
13+
app.config.from_object(config)
14+
15+
db.init_app(app)
16+
ma.init_app(app)
17+
af.init_app(app, db)
18+
19+
app.errorhandler(af.ApiError)(af.api_error_handler)
20+
21+
with app.app_context():
22+
from complete_db import blueprints # noqa
23+
from complete_db import models # noqa
24+
25+
db.create_all()
26+
27+
app.register_blueprint(blueprints.api_v1.bp, url_prefix="/v1")
28+
29+
return app
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from . import api_v1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from . import urls
2+
from .blueprint import bp
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import flask
2+
3+
4+
bp = flask.Blueprint(
5+
name="api_v1",
6+
import_name="api_v1",
7+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
from complete_db.ext import ma
2+
from complete_db.models import Author, Book
3+
4+
5+
class AuthorSchema(ma.SQLAlchemySchema):
6+
class Meta:
7+
model = Author
8+
load_instance = True
9+
10+
id = ma.Integer(dump_only=True)
11+
name = ma.String()
12+
13+
14+
class AuthorsBooksListKwargsSchema(ma.SQLAlchemySchema):
15+
class Meta:
16+
model = Author
17+
load_instance = True
18+
19+
id = ma.auto_field(data_key="author_id", required=True)
20+
21+
22+
class BookListSchema(ma.SQLAlchemySchema):
23+
class Meta:
24+
model = Book
25+
load_instance = True
26+
27+
id = ma.Integer()
28+
author = ma.Nested(AuthorSchema())
29+
title = ma.String()
30+
year = ma.Integer()
31+
description = ma.String()
32+
33+
34+
class BookCreateSchema(ma.SQLAlchemySchema):
35+
class Meta:
36+
model = Book
37+
load_instance = True
38+
39+
author_id = ma.Integer(required=True)
40+
title = ma.String(required=True)
41+
year = ma.Integer(required=True)
42+
description = ma.String()
43+
44+
45+
class BookDetailKwargsSchema(ma.SQLAlchemySchema):
46+
class Meta:
47+
model = Book
48+
load_instance = True
49+
50+
id = ma.Integer(required=True)
51+
52+
53+
class BookDetailBodySchema(ma.SQLAlchemySchema):
54+
class Meta:
55+
model = Book
56+
load_instance = True
57+
58+
id = ma.Integer(dump_only=True)
59+
title = ma.String(required=True)
60+
year = ma.Integer(required=True)
61+
description = ma.String(required=True)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from . import views
2+
from .blueprint import bp
3+
4+
5+
bp.add_url_rule(
6+
rule="/authors",
7+
endpoint="authors_index",
8+
view_func=views.AuthorsIndex.as_view("authors_index"),
9+
)
10+
bp.add_url_rule(
11+
rule="/authors/<author_id>/books",
12+
endpoint="authors_books_list",
13+
view_func=views.AuthorsBooksList.as_view("authors_books_list"),
14+
)
15+
bp.add_url_rule(
16+
rule="/books",
17+
endpoint="books_index",
18+
view_func=views.BooksIndex.as_view("books_index"),
19+
)
20+
bp.add_url_rule(
21+
rule="/books/<id>",
22+
endpoint="book_detail",
23+
view_func=views.BookDetail.as_view("book_detail"),
24+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
from complete_db.models import Author, Book
2+
3+
import flask_api_framework as af
4+
5+
from .schemas import (
6+
AuthorsBooksListKwargsSchema,
7+
AuthorSchema,
8+
BookCreateSchema,
9+
BookDetailBodySchema,
10+
BookDetailKwargsSchema,
11+
BookListSchema,
12+
)
13+
14+
15+
class AuthorsIndex(af.List, af.Create):
16+
"""
17+
List all authors.
18+
Same body schema for List and Create.
19+
"""
20+
21+
body_schema = AuthorSchema()
22+
23+
def get_instances(self):
24+
return Author.query.all()
25+
26+
27+
class AuthorsBooksList(af.List):
28+
"""
29+
List all books for a specific author defined by a view arg.
30+
"""
31+
32+
kwargs_schema = AuthorsBooksListKwargsSchema()
33+
body_schema = BookListSchema()
34+
35+
def get_instances(self):
36+
q = Book.query
37+
q = q.filter_by(author=self.loaded_kwargs)
38+
return q.all()
39+
40+
41+
class BooksIndex(af.List, af.Create):
42+
"""
43+
List all books, and Create new book.
44+
When listing, allow filtering via query params.
45+
Different body schemas for List and Create.
46+
"""
47+
48+
list_body_schema = BookListSchema()
49+
create_body_schema = BookCreateSchema()
50+
51+
def get_instances(self):
52+
return Book.query.all()
53+
54+
55+
class BookDetail(af.Read, af.Update, af.Delete):
56+
"""
57+
Read, Update, or Delete one specific book.
58+
Same body schema for Read, Update, Delete.
59+
"""
60+
61+
kwargs_schema = BookDetailKwargsSchema()
62+
body_schema = BookDetailBodySchema()

examples/complete_db/conf.py

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
class base:
2+
SQLALCHEMY_DATABASE_URI = "sqlite:///:memory:"
3+
JSON_SORT_KEYS = False
4+
FLASK_ADMIN_FLUID_LAYOUT = True
5+
FLASK_ADMIN_SWATCH = "yeti"
6+
7+
8+
class test(base):
9+
TESTING = True

examples/complete_db/ext.py

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import flask_marshmallow
2+
import flask_sqlalchemy
3+
4+
import flask_api_framework
5+
6+
7+
db = flask_sqlalchemy.SQLAlchemy()
8+
ma = flask_marshmallow.Marshmallow()
9+
af = flask_api_framework.ApiFramework()

examples/complete_db/models.py

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from complete_db.ext import db
2+
from sqlalchemy.orm import relationship
3+
4+
5+
class Author(db.Model):
6+
id = db.Column(db.Integer(), primary_key=True)
7+
name = db.Column(db.String(100))
8+
9+
books = relationship("Book", back_populates="author")
10+
11+
12+
class Book(db.Model):
13+
id = db.Column(db.Integer(), primary_key=True)
14+
title = db.Column(db.String(100))
15+
year = db.Column(db.Integer())
16+
description = db.Column(db.Text())
17+
18+
author_id = db.Column(db.Integer(), db.ForeignKey("author.id"))
19+
20+
author = relationship("Author", back_populates="books")

tests/examples/test_complete_db.py

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import pytest
2+
3+
4+
@pytest.mark.parametrize(
5+
"examples_client", [["complete_db", "create_app"]], indirect=True
6+
)
7+
def test_complete(examples_client):
8+
r = examples_client.post(
9+
"/v1/authors",
10+
json=dict(
11+
name="asdf",
12+
),
13+
)
14+
assert r.status_code == 201
15+
assert r.json["name"] == "asdf"
16+
17+
r = examples_client.post(
18+
"/v1/authors",
19+
json=dict(
20+
name="qwer",
21+
),
22+
)
23+
assert r.status_code == 201
24+
assert r.json["name"] == "qwer"
25+
26+
r = examples_client.get("/v1/authors")
27+
assert r.status_code == 200
28+
assert len(r.json["items"]) == 2
29+
30+
r = examples_client.post(
31+
"/v1/books",
32+
json=dict(
33+
author_id=1,
34+
title="asdf 1",
35+
year=1982,
36+
description="asdf1 asdf1 asdf1",
37+
),
38+
)
39+
assert r.status_code == 201
40+
41+
r = examples_client.post(
42+
"/v1/books",
43+
json=dict(
44+
author_id=1,
45+
title="asdf 2",
46+
year=1982,
47+
description="asdf2 asdf2 asdf2",
48+
),
49+
)
50+
assert r.status_code == 201
51+
52+
r = examples_client.post(
53+
"/v1/books",
54+
json=dict(
55+
author_id=2,
56+
title="asdf 3",
57+
year=1982,
58+
description="asdf3 asdf3 asdf3",
59+
),
60+
)
61+
assert r.status_code == 201
62+
63+
r = examples_client.post(
64+
"/v1/books",
65+
json=dict(
66+
year=1982,
67+
),
68+
)
69+
assert r.status_code == 400
70+
assert r.json["source"] == "body"
71+
72+
r = examples_client.get("/v1/books")
73+
assert r.status_code == 200
74+
assert len(r.json["items"]) == 3
75+
76+
r = examples_client.get("/v1/authors/1/books")
77+
assert r.status_code == 200
78+
assert len(r.json["items"]) == 2
79+
80+
r = examples_client.get("/v1/books/1")
81+
assert r.status_code == 200
82+
assert r.json["id"] == 1
83+
assert r.json["title"] == "asdf 1"
84+
85+
r = examples_client.get("/v1/books/asdf")
86+
assert r.status_code == 400
87+
assert r.json["source"] == "kwargs"
88+
89+
r = examples_client.patch(
90+
"/v1/books/1",
91+
json=dict(
92+
title="something else",
93+
),
94+
)
95+
assert r.status_code == 200
96+
assert r.json["title"] == "something else"
97+
98+
r = examples_client.get("/v1/books/1")
99+
assert r.status_code == 200
100+
assert r.json["id"] == 1
101+
assert r.json["title"] == "something else"
102+
assert r.json["description"] == "asdf1 asdf1 asdf1"
103+
104+
r = examples_client.put(
105+
"/v1/books/1",
106+
json=dict(
107+
title="zxcv",
108+
),
109+
)
110+
assert r.status_code == 400
111+
112+
r = examples_client.put(
113+
"/v1/books/1",
114+
json=dict(
115+
title="zxcv",
116+
year=1983,
117+
description="zxcv zxcv zxcv",
118+
),
119+
)
120+
assert r.status_code == 200
121+
122+
r = examples_client.get("/v1/books/1")
123+
assert r.status_code == 200
124+
assert r.json["id"] == 1
125+
assert r.json["title"] == "zxcv"
126+
assert r.json["year"] == 1983
127+
assert r.json["description"] == "zxcv zxcv zxcv"
128+
129+
r = examples_client.delete("/v1/books/1")
130+
assert r.status_code == 200
131+
132+
r = examples_client.get("/v1/books/1")
133+
assert r.status_code == 404

0 commit comments

Comments
 (0)