create REST API from model
parent
0f86af252f
commit
13665ce22c
|
@ -1,3 +1,4 @@
|
||||||
venv
|
venv
|
||||||
pizzatool.sqlite
|
pizzatool.sqlite
|
||||||
|
__pycache__
|
||||||
*.swp
|
*.swp
|
||||||
|
|
130
pizzatool/api.py
130
pizzatool/api.py
|
@ -1,129 +1,9 @@
|
||||||
from fastapi import Depends, FastAPI, HTTPException, Response, status
|
from fastapi import FastAPI
|
||||||
from logging import getLogger
|
from pizzatool.api_helpers import CRUDMapper, RelationshipMapper
|
||||||
from ormantic.exceptions import NoMatch
|
|
||||||
from pizzatool.model import Ingredient, Pizza, PizzaIngredient
|
from pizzatool.model import Ingredient, Pizza, PizzaIngredient
|
||||||
|
|
||||||
|
|
||||||
logger = getLogger(__name__)
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
CRUDMapper(app, Ingredient)
|
||||||
|
CRUDMapper(app, Pizza)
|
||||||
@app.get('/ingredients')
|
RelationshipMapper(app, Pizza, Ingredient, PizzaIngredient)
|
||||||
async def read_ingredients():
|
|
||||||
return await Ingredient.objects.all()
|
|
||||||
|
|
||||||
|
|
||||||
@app.post('/ingredients', status_code=status.HTTP_201_CREATED)
|
|
||||||
async def create_ingredient(name: str, response: Response):
|
|
||||||
ingredient = await Ingredient.objects.create(name=name)
|
|
||||||
response.headers['Location'] = 'ingredients/%d' % ingredient.id
|
|
||||||
return ingredient
|
|
||||||
|
|
||||||
|
|
||||||
async def get_ingredient(ingredient_id: int):
|
|
||||||
try:
|
|
||||||
return await Ingredient.objects.get(id=ingredient_id)
|
|
||||||
except NoMatch:
|
|
||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
|
||||||
|
|
||||||
|
|
||||||
@app.get('/ingredients/{ingredient_id}')
|
|
||||||
async def read_ingredient(ingredient: Ingredient = Depends(get_ingredient)):
|
|
||||||
return ingredient
|
|
||||||
|
|
||||||
|
|
||||||
@app.put('/ingredients/{ingredient_id}',
|
|
||||||
status_code=status.HTTP_204_NO_CONTENT)
|
|
||||||
async def update_ingredient(
|
|
||||||
name: str,
|
|
||||||
ingredient: Ingredient = Depends(get_ingredient)):
|
|
||||||
await ingredient.update(name=name)
|
|
||||||
|
|
||||||
|
|
||||||
@app.delete('/ingredients/{ingredient_id}',
|
|
||||||
status_code=status.HTTP_204_NO_CONTENT)
|
|
||||||
async def delete_ingredient(ingredient: Ingredient = Depends(get_ingredient)):
|
|
||||||
await ingredient.delete()
|
|
||||||
|
|
||||||
|
|
||||||
@app.get('/pizzas')
|
|
||||||
async def read_pizzas():
|
|
||||||
return await Pizza.objects.all()
|
|
||||||
|
|
||||||
|
|
||||||
@app.post('/pizzas', status_code=status.HTTP_201_CREATED)
|
|
||||||
async def create_pizza(name: str, response: Response):
|
|
||||||
pizza = await Pizza.objects.create(name=name)
|
|
||||||
response.headers['Location'] = 'pizzas/%d' % pizza.id
|
|
||||||
return pizza
|
|
||||||
|
|
||||||
|
|
||||||
async def get_pizza(pizza_id: int):
|
|
||||||
try:
|
|
||||||
return await Pizza.objects.get(id=pizza_id)
|
|
||||||
except NoMatch:
|
|
||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
|
||||||
|
|
||||||
|
|
||||||
@app.get('/pizzas/{pizza_id}')
|
|
||||||
async def read_pizza(pizza: Pizza = Depends(get_pizza)):
|
|
||||||
return pizza
|
|
||||||
|
|
||||||
|
|
||||||
@app.put('/pizzas/{pizza_id}', status_code=status.HTTP_204_NO_CONTENT)
|
|
||||||
async def update_pizza(name: str, pizza: Pizza = Depends(get_pizza)):
|
|
||||||
await pizza.update(name=name)
|
|
||||||
|
|
||||||
|
|
||||||
@app.delete('/pizzas/{pizza_id}', status_code=status.HTTP_204_NO_CONTENT)
|
|
||||||
async def delete_pizza(pizza: Pizza = Depends(get_pizza)):
|
|
||||||
await pizza.delete()
|
|
||||||
|
|
||||||
|
|
||||||
@app.get('/pizzas/{pizza_id}/ingredients')
|
|
||||||
async def read_pizza_ingredients(pizza: Pizza = Depends(get_pizza)):
|
|
||||||
return await PizzaIngredient.objects.select_related('ingredient') \
|
|
||||||
.filter(pizza=pizza).all()
|
|
||||||
|
|
||||||
|
|
||||||
@app.post('/pizza/{pizza_id}/ingredients', status_code=status.HTTP_201_CREATED)
|
|
||||||
async def create_pizza_ingredient(
|
|
||||||
response: Response,
|
|
||||||
pizza: Pizza = Depends(get_pizza),
|
|
||||||
ingredient: Ingredient = Depends(get_ingredient)):
|
|
||||||
pizza_ingredient = await PizzaIngredient.objects.create(
|
|
||||||
pizza=pizza,
|
|
||||||
ingredient=ingredient)
|
|
||||||
response.headers['Location'] = 'ingredients/%d' % pizza_ingredient.id
|
|
||||||
return pizza_ingredient
|
|
||||||
|
|
||||||
|
|
||||||
async def get_pizza_ingredient(
|
|
||||||
pizza_ingredient_id: int,
|
|
||||||
pizza: Pizza = Depends(get_pizza)):
|
|
||||||
try:
|
|
||||||
return await PizzaIngredient.objects.select_related('ingredient') \
|
|
||||||
.get(id=pizza_ingredient_id, pizza=pizza)
|
|
||||||
except NoMatch:
|
|
||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
|
||||||
|
|
||||||
|
|
||||||
@app.get('/pizzas/{pizza_id}/ingredients/{pizza_ingredient_id}')
|
|
||||||
async def read_pizza_ingredient(
|
|
||||||
pizza_ingredient: PizzaIngredient = Depends(get_pizza_ingredient)):
|
|
||||||
return pizza_ingredient
|
|
||||||
|
|
||||||
|
|
||||||
@app.put('/pizzas/{pizza_id}/ingredients/{pizza_ingredient_id}',
|
|
||||||
status_code=status.HTTP_204_NO_CONTENT)
|
|
||||||
async def update_pizza_ingredient(
|
|
||||||
pizza_ingredient: PizzaIngredient = Depends(get_pizza_ingredient),
|
|
||||||
ingredient: Ingredient = Depends(get_ingredient)):
|
|
||||||
await pizza_ingredient.update(ingredient=ingredient.id)
|
|
||||||
|
|
||||||
|
|
||||||
@app.delete('/pizzas/{pizza_id}/ingredients/{pizza_ingredient_id}',
|
|
||||||
status_code=status.HTTP_204_NO_CONTENT)
|
|
||||||
async def delete_pizza_ingredient(
|
|
||||||
pizza_ingredient: PizzaIngredient = Depends(get_pizza_ingredient)):
|
|
||||||
await pizza_ingredient.delete()
|
|
||||||
|
|
|
@ -0,0 +1,103 @@
|
||||||
|
from fastapi import Depends, HTTPException, Response, status
|
||||||
|
from inspect import Parameter, Signature
|
||||||
|
from ormantic.exceptions import NoMatch
|
||||||
|
from sqlite3 import IntegrityError
|
||||||
|
|
||||||
|
|
||||||
|
def CRUDMapper(app, ORMClass):
|
||||||
|
name = ORMClass.Mapping.table_name
|
||||||
|
collection_path = '/%ss' % name
|
||||||
|
item_path = '/%ss/{id}' % name
|
||||||
|
|
||||||
|
@app.get(collection_path)
|
||||||
|
async def read_all():
|
||||||
|
return await ORMClass.objects.all()
|
||||||
|
|
||||||
|
@app.post(collection_path, status_code=status.HTTP_201_CREATED)
|
||||||
|
async def create(values: ORMClass, response: Response):
|
||||||
|
try:
|
||||||
|
item = await ORMClass.objects.create(**values.__dict__)
|
||||||
|
response.headers['Location'] = '%ss/%d' % (name, item.id)
|
||||||
|
return item
|
||||||
|
except IntegrityError:
|
||||||
|
raise HTTPException(status_code=status.HTTP_409_CONFLICT)
|
||||||
|
|
||||||
|
async def get_item(id: int):
|
||||||
|
try:
|
||||||
|
return await ORMClass.objects.get(id=id)
|
||||||
|
except NoMatch:
|
||||||
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
@app.get(item_path)
|
||||||
|
async def read(item=Depends(get_item)):
|
||||||
|
return item
|
||||||
|
|
||||||
|
@app.put(item_path, status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
async def update(values: ORMClass, item=Depends(get_item)):
|
||||||
|
await item.update(**values.__dict__)
|
||||||
|
|
||||||
|
@app.delete(item_path, status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
async def delete(item=Depends(get_item)):
|
||||||
|
await item.delete()
|
||||||
|
|
||||||
|
|
||||||
|
def RelationshipMapper(app, ORMClass1, ORMClass2, ORMRelationship):
|
||||||
|
scope_name = ORMClass1.Mapping.table_name
|
||||||
|
item_name = ORMClass2.Mapping.table_name
|
||||||
|
collection_path = '/%ss/{id}/%ss' % (scope_name, item_name)
|
||||||
|
item_path = '/%ss/{id}/%ss/{id2}' % (scope_name, item_name)
|
||||||
|
|
||||||
|
async def get_scope(id: int):
|
||||||
|
try:
|
||||||
|
return await ORMClass1.objects.get(id=id)
|
||||||
|
except NoMatch:
|
||||||
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
async def get_item(**kwargs):
|
||||||
|
try:
|
||||||
|
return await ORMClass2.objects.get(id=kwargs['%s_id' % item_name])
|
||||||
|
except NoMatch:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY)
|
||||||
|
get_item.__signature__ = Signature(parameters=[Parameter(
|
||||||
|
'%s_id' % item_name, Parameter.KEYWORD_ONLY, annotation=int)])
|
||||||
|
|
||||||
|
async def get_relationship(id2: int, scope=Depends(get_scope)):
|
||||||
|
try:
|
||||||
|
return await ORMRelationship.objects.select_related(item_name) \
|
||||||
|
.get(id=id2, **{scope_name: scope})
|
||||||
|
except NoMatch:
|
||||||
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
@app.get(collection_path)
|
||||||
|
async def read_all(scope=Depends(get_scope)):
|
||||||
|
return await ORMRelationship.objects.select_related(item_name) \
|
||||||
|
.filter(**{scope_name: scope}).all()
|
||||||
|
|
||||||
|
@app.post(collection_path, status_code=status.HTTP_201_CREATED)
|
||||||
|
async def create(
|
||||||
|
response: Response,
|
||||||
|
scope=Depends(get_scope),
|
||||||
|
item=Depends(get_item)):
|
||||||
|
try:
|
||||||
|
relationship = await ORMRelationship.objects.create(
|
||||||
|
**{scope_name: scope, item_name: item})
|
||||||
|
response.headers['Location'] = \
|
||||||
|
'%ss/%d' % (item_name, relationship.id)
|
||||||
|
return relationship
|
||||||
|
except IntegrityError:
|
||||||
|
raise HTTPException(status_code=status.HTTP_409_CONFLICT)
|
||||||
|
|
||||||
|
@app.get(item_path)
|
||||||
|
async def read(relationship=Depends(get_relationship)):
|
||||||
|
return relationship
|
||||||
|
|
||||||
|
@app.put(item_path, status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
async def update(
|
||||||
|
item=Depends(get_item),
|
||||||
|
relationship=Depends(get_relationship)):
|
||||||
|
await relationship.update(**{item_name: item.id})
|
||||||
|
|
||||||
|
@app.delete(item_path, status_code=status.HTTP_204_NO_CONTENT)
|
||||||
|
async def delete(relationship=Depends(get_relationship)):
|
||||||
|
await relationship.delete()
|
|
@ -12,7 +12,7 @@ class Ingredient(Model):
|
||||||
name: Text()
|
name: Text()
|
||||||
|
|
||||||
class Mapping:
|
class Mapping:
|
||||||
table_name = 'ingredients'
|
table_name = 'ingredient'
|
||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ class Pizza(Model):
|
||||||
name: Text()
|
name: Text()
|
||||||
|
|
||||||
class Mapping:
|
class Mapping:
|
||||||
table_name = 'pizzas'
|
table_name = 'pizza'
|
||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ class PizzaIngredient(Model):
|
||||||
ingredient: ForeignKey(Ingredient)
|
ingredient: ForeignKey(Ingredient)
|
||||||
|
|
||||||
class Mapping:
|
class Mapping:
|
||||||
table_name = 'pizzaingredients'
|
table_name = 'pizzaingredient'
|
||||||
metadata = metadata
|
metadata = metadata
|
||||||
database = database
|
database = database
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue