71 changed files with 656 additions and 656 deletions
@ -1,10 +0,0 @@ |
|||||
from fastapi import FastAPI |
|
||||
from fastapi import FastAPI |
|
||||
|
|
||||
app = FastAPI() |
|
||||
|
|
||||
@app.get("/items/{item_id}") |
|
||||
async def read_item(item_id: str, q: str = None): |
|
||||
if q: |
|
||||
return {"item_id": item_id, "q": q} |
|
||||
return {"item_id": item_id} |
|
@ -1,12 +1,10 @@ |
|||||
from fastapi import FastAPI |
from fastapi import FastAPI |
||||
|
from fastapi import FastAPI |
||||
|
|
||||
app = FastAPI() |
app = FastAPI() |
||||
|
|
||||
@app.get("/items/{item_id}") |
@app.get("/items/{item_id}") |
||||
async def read_item(item_id: str, q: str = None, short: bool = False): |
async def read_item(item_id: str, q: str = None): |
||||
item = {"item_id": item_id} |
|
||||
if q: |
if q: |
||||
item.update({"q": q}) |
return {"item_id": item_id, "q": q} |
||||
if not short: |
return {"item_id": item_id} |
||||
item.update({"description": "This is an amazing item that has a long description"}) |
|
||||
return item |
|
||||
|
@ -1,15 +1,12 @@ |
|||||
from fastapi import FastAPI |
from fastapi import FastAPI |
||||
from pydantic import BaseModel |
|
||||
|
|
||||
class Item(BaseModel): |
|
||||
name: str |
|
||||
description: str = None |
|
||||
price: float |
|
||||
tax: float = None |
|
||||
|
|
||||
|
|
||||
app = FastAPI() |
app = FastAPI() |
||||
|
|
||||
@app.post("/items/") |
@app.get("/users/{user_id}/items/{item_id}") |
||||
async def create_item(item: Item): |
async def read_user_item(user_id: int, item_id: str, needy: str, q: str = None, short: bool = False): |
||||
|
item = {"item_id": item_id, "owner_id": user_id, "needy": needy} |
||||
|
if q: |
||||
|
item.update({"q": q}) |
||||
|
if not short: |
||||
|
item.update({"description": "This is an amazing item that has a long description"}) |
||||
return item |
return item |
||||
|
@ -1,11 +1,18 @@ |
|||||
from fastapi import FastAPI, Query |
from fastapi import FastAPI |
||||
|
from pydantic import BaseModel |
||||
|
|
||||
|
class Item(BaseModel): |
||||
|
name: str |
||||
|
description: str = None |
||||
|
price: float |
||||
|
tax: float = None |
||||
|
|
||||
app = FastAPI() |
|
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
@app.get("/items/") |
@app.put("/items/{item_id}") |
||||
async def read_items(q: str = Query(None, max_length=50)): |
async def create_item(item_id: int, item: Item, q: str = None): |
||||
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} |
result = {"item_id": item_id, **item.dict()} |
||||
if q: |
if q: |
||||
results.update({"q": q}) |
result.update({"q": q}) |
||||
return results |
return result |
||||
|
@ -1,21 +0,0 @@ |
|||||
from fastapi import FastAPI, Query |
|
||||
|
|
||||
app = FastAPI() |
|
||||
|
|
||||
|
|
||||
@app.get("/items/") |
|
||||
async def read_items( |
|
||||
q: str = Query( |
|
||||
"fixedquery", |
|
||||
alias="item-query", |
|
||||
title="Query string", |
|
||||
description="Query string for the items to search in the database that have a good match", |
|
||||
min_length=3, |
|
||||
max_length=50, |
|
||||
regex="^fixedquery$", |
|
||||
) |
|
||||
): |
|
||||
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} |
|
||||
if q: |
|
||||
results.update({"q": q}) |
|
||||
return results |
|
@ -1,21 +0,0 @@ |
|||||
from fastapi import FastAPI, Query |
|
||||
|
|
||||
app = FastAPI() |
|
||||
|
|
||||
|
|
||||
@app.get("/items/") |
|
||||
async def read_items( |
|
||||
q: str = Query( |
|
||||
..., |
|
||||
alias="item-query", |
|
||||
title="Query string", |
|
||||
description="Query string for the items to search in the database that have a good match", |
|
||||
min_length=3, |
|
||||
max_length=50, |
|
||||
regex="^fixedquery$", |
|
||||
) |
|
||||
): |
|
||||
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} |
|
||||
if q: |
|
||||
results.update({"q": q}) |
|
||||
return results |
|
@ -1,14 +1,21 @@ |
|||||
from fastapi import FastAPI, Query, Path |
from fastapi import FastAPI, Query |
||||
|
|
||||
app = FastAPI() |
app = FastAPI() |
||||
|
|
||||
|
|
||||
@app.get("/items/{item_id}") |
@app.get("/items/") |
||||
async def read_items( |
async def read_items( |
||||
item_id: int = Path(..., title="The ID of the item to get"), |
q: str = Query( |
||||
q: str = Query(None, alias="item-query"), |
None, |
||||
|
alias="item-query", |
||||
|
title="Query string", |
||||
|
description="Query string for the items to search in the database that have a good match", |
||||
|
min_length=3, |
||||
|
max_length=50, |
||||
|
regex="^fixedquery$", |
||||
|
) |
||||
): |
): |
||||
results = {"item_id": item_id} |
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} |
||||
if q: |
if q: |
||||
results.update({"q": q}) |
results.update({"q": q}) |
||||
return results |
return results |
||||
|
@ -1,14 +1,21 @@ |
|||||
from fastapi import FastAPI, Query, Path |
from fastapi import FastAPI, Query |
||||
|
|
||||
app = FastAPI() |
app = FastAPI() |
||||
|
|
||||
|
|
||||
@app.get("/items/{item_id}") |
@app.get("/items/") |
||||
async def read_items( |
async def read_items( |
||||
q: str, |
q: str = Query( |
||||
item_id: int = Path(..., title="The ID of the item to get"), |
..., |
||||
|
alias="item-query", |
||||
|
title="Query string", |
||||
|
description="Query string for the items to search in the database that have a good match", |
||||
|
min_length=3, |
||||
|
max_length=50, |
||||
|
regex="^fixedquery$", |
||||
|
) |
||||
): |
): |
||||
results = {"item_id": item_id} |
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} |
||||
if q: |
if q: |
||||
results.update({"q": q}) |
results.update({"q": q}) |
||||
return results |
return results |
||||
|
@ -1,15 +1,22 @@ |
|||||
from fastapi import FastAPI, Query, Path |
from fastapi import FastAPI, Query |
||||
|
|
||||
app = FastAPI() |
app = FastAPI() |
||||
|
|
||||
|
|
||||
@app.get("/items/{item_id}") |
@app.get("/items/") |
||||
async def read_items( |
async def read_items( |
||||
*, |
q: str = Query( |
||||
item_id: int = Path(..., title="The ID of the item to get"), |
None, |
||||
q: str, |
alias="item-query", |
||||
|
title="Query string", |
||||
|
description="Query string for the items to search in the database that have a good match", |
||||
|
min_length=3, |
||||
|
max_length=50, |
||||
|
regex="^fixedquery$", |
||||
|
deprecated=True, |
||||
|
) |
||||
): |
): |
||||
results = {"item_id": item_id} |
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} |
||||
if q: |
if q: |
||||
results.update({"q": q}) |
results.update({"q": q}) |
||||
return results |
return results |
||||
|
@ -1,26 +1,15 @@ |
|||||
from fastapi import FastAPI, Query, Path |
from fastapi import FastAPI, Query, Path |
||||
from pydantic import BaseModel |
|
||||
|
|
||||
app = FastAPI() |
app = FastAPI() |
||||
|
|
||||
|
|
||||
class Item(BaseModel): |
@app.get("/items/{item_id}") |
||||
name: str |
async def read_items( |
||||
description: str = None |
|
||||
price: float |
|
||||
tax: float = None |
|
||||
|
|
||||
|
|
||||
@app.put("/items/{item_id}") |
|
||||
async def update_item( |
|
||||
*, |
*, |
||||
item_id: int = Path(..., title="The ID of the item to get", ge=0, le=1000), |
item_id: int = Path(..., title="The ID of the item to get", ge=1), |
||||
q: str, |
q: str, |
||||
item: Item = None, |
|
||||
): |
): |
||||
results = {"item_id": item_id} |
results = {"item_id": item_id} |
||||
if q: |
if q: |
||||
results.update({"q": q}) |
results.update({"q": q}) |
||||
if item: |
|
||||
results.update({"item": item}) |
|
||||
return results |
return results |
||||
|
@ -1,27 +1,15 @@ |
|||||
from fastapi import FastAPI, Query, Path |
from fastapi import FastAPI, Query, Path |
||||
from pydantic import BaseModel |
|
||||
|
|
||||
app = FastAPI() |
app = FastAPI() |
||||
|
|
||||
|
|
||||
class Item(BaseModel): |
@app.get("/items/{item_id}") |
||||
name: str |
async def read_items( |
||||
description: str = None |
|
||||
price: float |
|
||||
tax: float = None |
|
||||
|
|
||||
|
|
||||
class User(BaseModel): |
|
||||
username: str |
|
||||
full_name: str = None |
|
||||
|
|
||||
|
|
||||
@app.put("/items/{item_id}") |
|
||||
async def update_item( |
|
||||
*, |
*, |
||||
item_id: int, |
item_id: int = Path(..., title="The ID of the item to get", gt=0, le=1000), |
||||
item: Item, |
q: str, |
||||
user: User, |
|
||||
): |
): |
||||
results = {"item_id": item_id, "item": item, "user": user} |
results = {"item_id": item_id} |
||||
|
if q: |
||||
|
results.update({"q": q}) |
||||
return results |
return results |
||||
|
@ -1,28 +1,16 @@ |
|||||
from fastapi import FastAPI, Query, Path, Body |
from fastapi import FastAPI, Query, Path |
||||
from pydantic import BaseModel |
|
||||
|
|
||||
app = FastAPI() |
app = FastAPI() |
||||
|
|
||||
|
|
||||
class Item(BaseModel): |
@app.get("/items/{item_id}") |
||||
name: str |
async def read_items( |
||||
description: str = None |
|
||||
price: float |
|
||||
tax: float = None |
|
||||
|
|
||||
|
|
||||
class User(BaseModel): |
|
||||
username: str |
|
||||
full_name: str = None |
|
||||
|
|
||||
|
|
||||
@app.put("/items/{item_id}") |
|
||||
async def update_item( |
|
||||
*, |
*, |
||||
item_id: int, |
item_id: int = Path(..., title="The ID of the item to get", ge=0, le=1000), |
||||
item: Item, |
q: str, |
||||
user: User, |
size: float = Query(..., gt=0, lt=10.5) |
||||
access_token: str = Body(...), |
|
||||
): |
): |
||||
results = {"item_id": item_id, "item": item, "user": user, "access_token": access_token} |
results = {"item_id": item_id} |
||||
|
if q: |
||||
|
results.update({"q": q}) |
||||
return results |
return results |
||||
|
@ -1,21 +1,28 @@ |
|||||
from fastapi import FastAPI, Query, Path, Body |
from fastapi import FastAPI, Query, Path, Body |
||||
from pydantic import BaseModel, Schema |
from pydantic import BaseModel |
||||
|
|
||||
app = FastAPI() |
app = FastAPI() |
||||
|
|
||||
|
|
||||
class Item(BaseModel): |
class Item(BaseModel): |
||||
name: str |
name: str |
||||
description: str = Schema(None, title="The description of the item", max_length=300) |
description: str = None |
||||
price: float = Schema(..., gt=0, description="The price must be greater than zero") |
price: float |
||||
tax: float = None |
tax: float = None |
||||
|
|
||||
|
|
||||
|
class User(BaseModel): |
||||
|
username: str |
||||
|
full_name: str = None |
||||
|
|
||||
|
|
||||
@app.put("/items/{item_id}") |
@app.put("/items/{item_id}") |
||||
async def update_item( |
async def update_item( |
||||
*, |
*, |
||||
item_id: int, |
item_id: int, |
||||
item: Item = Body(..., embed=True), |
item: Item, |
||||
|
user: User, |
||||
|
access_token: str = Body(...), |
||||
): |
): |
||||
results = {"item_id": item_id, "item": item} |
results = {"item_id": item_id, "item": item, "user": user, "access_token": access_token} |
||||
return results |
return results |
||||
|
@ -1,23 +1,21 @@ |
|||||
from fastapi import Body, FastAPI, Path, Query |
from fastapi import FastAPI, Query, Path, Body |
||||
from pydantic import BaseModel |
from pydantic import BaseModel, Schema |
||||
from typing import Set |
|
||||
|
|
||||
app = FastAPI() |
app = FastAPI() |
||||
|
|
||||
|
|
||||
class Item(BaseModel): |
class Item(BaseModel): |
||||
name: str |
name: str |
||||
description: str = None |
description: str = Schema(None, title="The description of the item", max_length=300) |
||||
price: float |
price: float = Schema(..., gt=0, description="The price must be greater than zero") |
||||
tax: float = None |
tax: float = None |
||||
tags: Set[str] = [] |
|
||||
|
|
||||
|
|
||||
@app.put("/items/{item_id}") |
@app.put("/items/{item_id}") |
||||
async def update_item( |
async def update_item( |
||||
*, |
*, |
||||
item_id: int, |
item_id: int, |
||||
item: Item, |
item: Item = Body(..., embed=True), |
||||
): |
): |
||||
results = {"item_id": item_id, "item": item} |
results = {"item_id": item_id, "item": item} |
||||
return results |
return results |
||||
|
@ -1,20 +1,11 @@ |
|||||
from fastapi import Body, FastAPI, Path, Query |
from fastapi import FastAPI, Header |
||||
from pydantic import BaseModel |
from pydantic import BaseModel |
||||
from pydantic.types import EmailStr |
from pydantic.types import UrlStr |
||||
from typing import Set, List |
from typing import Set, List |
||||
|
|
||||
app = FastAPI() |
app = FastAPI() |
||||
|
|
||||
|
|
||||
class UserIn(BaseModel): |
@app.get("/items/") |
||||
username: str |
async def read_items(*, accept_encoding: str = Header(None)): |
||||
password: str |
return {"Accept-Encoding": accept_encoding} |
||||
email: EmailStr |
|
||||
full_name: str = None |
|
||||
|
|
||||
|
|
||||
|
|
||||
# Don't do this in production! |
|
||||
@app.post("/user/", response_model=UserIn) |
|
||||
async def create_user(*, user: UserIn): |
|
||||
return user |
|
||||
|
@ -1,24 +1,11 @@ |
|||||
from fastapi import Body, FastAPI, Path, Query |
from fastapi import FastAPI, Header |
||||
from pydantic import BaseModel |
from pydantic import BaseModel |
||||
from pydantic.types import EmailStr |
from pydantic.types import UrlStr |
||||
from typing import Set, List |
from typing import Set, List |
||||
|
|
||||
app = FastAPI() |
app = FastAPI() |
||||
|
|
||||
|
|
||||
class UserIn(BaseModel): |
@app.get("/items/") |
||||
username: str |
async def read_items(*, strange_header: str = Header(None, convert_underscores=False)): |
||||
password: str |
return {"strange_header": strange_header} |
||||
email: EmailStr |
|
||||
full_name: str = None |
|
||||
|
|
||||
|
|
||||
class UserOut(BaseModel): |
|
||||
username: str |
|
||||
email: EmailStr |
|
||||
full_name: str = None |
|
||||
|
|
||||
|
|
||||
@app.post("/user/", response_model=UserOut) |
|
||||
async def create_user(*, user: UserIn): |
|
||||
return user |
|
||||
|
@ -1,43 +1,19 @@ |
|||||
from fastapi import Body, FastAPI, Path, Query |
from fastapi import Body, FastAPI, Path, Query |
||||
from pydantic import BaseModel |
from pydantic import BaseModel |
||||
from pydantic.types import EmailStr |
from pydantic.types import UrlStr |
||||
from typing import Set, List |
from typing import Set, List |
||||
from uuid import UUID, uuid4 |
|
||||
|
|
||||
app = FastAPI() |
app = FastAPI() |
||||
|
|
||||
|
|
||||
class UserIn(BaseModel): |
class Item(BaseModel): |
||||
username: str |
name: str |
||||
password: str |
description: str = None |
||||
email: EmailStr |
price: float |
||||
full_name: str = None |
tax: float = None |
||||
|
tags: Set[str] = [] |
||||
|
|
||||
|
|
||||
class UserOut(BaseModel): |
@app.post("/items/", response_model=Item) |
||||
username: str |
async def create_item(*, item: Item): |
||||
email: EmailStr |
return item |
||||
full_name: str = None |
|
||||
|
|
||||
|
|
||||
class UserInDB(BaseModel): |
|
||||
username: str |
|
||||
hashed_password: str |
|
||||
email: EmailStr |
|
||||
full_name: str = None |
|
||||
|
|
||||
|
|
||||
def fake_password_hasher(raw_password: str): |
|
||||
return "supersecret" + raw_password |
|
||||
|
|
||||
|
|
||||
def fake_save_user(user_in: UserIn): |
|
||||
hashed_password = fake_password_hasher(user_in.password) |
|
||||
user_in_db = UserInDB(**user_in.dict(), hashed_password=hashed_password) |
|
||||
print("User saved! ..not really") |
|
||||
return user_in_db |
|
||||
|
|
||||
@app.post("/user/", response_model=UserOut) |
|
||||
async def create_user(*, user_in: UserIn): |
|
||||
user_saved = fake_save_user(user_in) |
|
||||
return user_saved |
|
||||
|
@ -1,37 +0,0 @@ |
|||||
from fastapi import Body, FastAPI, Path, Query |
|
||||
from pydantic import BaseModel |
|
||||
from pydantic.types import EmailStr |
|
||||
from typing import Set, List |
|
||||
|
|
||||
app = FastAPI() |
|
||||
|
|
||||
class UserBase(BaseModel): |
|
||||
username: str |
|
||||
email: EmailStr |
|
||||
full_name: str = None |
|
||||
|
|
||||
class UserIn(UserBase): |
|
||||
password: str |
|
||||
|
|
||||
|
|
||||
class UserOut(UserBase): |
|
||||
pass |
|
||||
|
|
||||
class UserInDB(UserBase): |
|
||||
hashed_password: str |
|
||||
|
|
||||
|
|
||||
def fake_password_hasher(raw_password: str): |
|
||||
return "supersecret" + raw_password |
|
||||
|
|
||||
|
|
||||
def fake_save_user(user_in: UserIn): |
|
||||
hashed_password = fake_password_hasher(user_in.password) |
|
||||
user_in_db = UserInDB(**user_in.dict(), hashed_password=hashed_password) |
|
||||
print("User saved! ..not really") |
|
||||
return user_in_db |
|
||||
|
|
||||
@app.post("/user/", response_model=UserOut) |
|
||||
async def create_user(*, user_in: UserIn): |
|
||||
user_saved = fake_save_user(user_in) |
|
||||
return user_saved |
|
@ -1,11 +0,0 @@ |
|||||
from fastapi import Body, FastAPI, Path, Query, Form |
|
||||
from pydantic import BaseModel |
|
||||
from pydantic.types import EmailStr |
|
||||
from typing import Set, List |
|
||||
|
|
||||
app = FastAPI() |
|
||||
|
|
||||
|
|
||||
@app.post("/login/") |
|
||||
async def login(*, username: str = Form(...), password: str = Form(...)): |
|
||||
return {"username": username} |
|
@ -1,11 +0,0 @@ |
|||||
from fastapi import Body, FastAPI, Path, Query, File |
|
||||
from pydantic import BaseModel |
|
||||
from pydantic.types import EmailStr |
|
||||
from typing import Set, List |
|
||||
|
|
||||
app = FastAPI() |
|
||||
|
|
||||
|
|
||||
@app.post("/files/") |
|
||||
async def create_file(*, file: bytes = File(...)): |
|
||||
return {"file_size": len(file)} |
|
@ -1,11 +0,0 @@ |
|||||
from fastapi import Body, FastAPI, Path, Query, File, Form |
|
||||
from pydantic import BaseModel |
|
||||
from pydantic.types import EmailStr |
|
||||
from typing import Set, List |
|
||||
|
|
||||
app = FastAPI() |
|
||||
|
|
||||
|
|
||||
@app.post("/files/") |
|
||||
async def create_file(*, file: bytes = File(...), token: str = Form(...)): |
|
||||
return {"file_size": len(file), "token": token} |
|
@ -0,0 +1,20 @@ |
|||||
|
from fastapi import Body, FastAPI, Path, Query |
||||
|
from pydantic import BaseModel |
||||
|
from pydantic.types import EmailStr |
||||
|
from typing import Set, List |
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
|
||||
|
class UserIn(BaseModel): |
||||
|
username: str |
||||
|
password: str |
||||
|
email: EmailStr |
||||
|
full_name: str = None |
||||
|
|
||||
|
|
||||
|
|
||||
|
# Don't do this in production! |
||||
|
@app.post("/user/", response_model=UserIn) |
||||
|
async def create_user(*, user: UserIn): |
||||
|
return user |
@ -1,20 +1,24 @@ |
|||||
from fastapi import Body, FastAPI, Path, Query |
from fastapi import Body, FastAPI, Path, Query |
||||
from starlette.status import HTTP_201_CREATED |
|
||||
from pydantic import BaseModel |
from pydantic import BaseModel |
||||
from pydantic.types import UrlStr |
from pydantic.types import EmailStr |
||||
from typing import Set, List |
from typing import Set, List |
||||
|
|
||||
app = FastAPI() |
app = FastAPI() |
||||
|
|
||||
|
|
||||
class Item(BaseModel): |
class UserIn(BaseModel): |
||||
name: str |
username: str |
||||
description: str = None |
password: str |
||||
price: float |
email: EmailStr |
||||
tax: float = None |
full_name: str = None |
||||
tags: Set[str] = [] |
|
||||
|
|
||||
|
|
||||
@app.post("/items/", response_model=Item, status_code=HTTP_201_CREATED) |
class UserOut(BaseModel): |
||||
async def create_item(*, item: Item): |
username: str |
||||
return item |
email: EmailStr |
||||
|
full_name: str = None |
||||
|
|
||||
|
|
||||
|
@app.post("/user/", response_model=UserOut) |
||||
|
async def create_user(*, user: UserIn): |
||||
|
return user |
||||
|
@ -1,20 +1,43 @@ |
|||||
from fastapi import Body, FastAPI, Path, Query |
from fastapi import Body, FastAPI, Path, Query |
||||
from starlette.status import HTTP_201_CREATED |
|
||||
from pydantic import BaseModel |
from pydantic import BaseModel |
||||
from pydantic.types import UrlStr |
from pydantic.types import EmailStr |
||||
from typing import Set, List |
from typing import Set, List |
||||
|
from uuid import UUID, uuid4 |
||||
|
|
||||
app = FastAPI() |
app = FastAPI() |
||||
|
|
||||
|
|
||||
class Item(BaseModel): |
class UserIn(BaseModel): |
||||
name: str |
username: str |
||||
description: str = None |
password: str |
||||
price: float |
email: EmailStr |
||||
tax: float = None |
full_name: str = None |
||||
tags: Set[str] = [] |
|
||||
|
|
||||
|
|
||||
@app.post("/items/", response_model=Item, tags=["items"]) |
class UserOut(BaseModel): |
||||
async def create_item(*, item: Item): |
username: str |
||||
return item |
email: EmailStr |
||||
|
full_name: str = None |
||||
|
|
||||
|
|
||||
|
class UserInDB(BaseModel): |
||||
|
username: str |
||||
|
hashed_password: str |
||||
|
email: EmailStr |
||||
|
full_name: str = None |
||||
|
|
||||
|
|
||||
|
def fake_password_hasher(raw_password: str): |
||||
|
return "supersecret" + raw_password |
||||
|
|
||||
|
|
||||
|
def fake_save_user(user_in: UserIn): |
||||
|
hashed_password = fake_password_hasher(user_in.password) |
||||
|
user_in_db = UserInDB(**user_in.dict(), hashed_password=hashed_password) |
||||
|
print("User saved! ..not really") |
||||
|
return user_in_db |
||||
|
|
||||
|
@app.post("/user/", response_model=UserOut) |
||||
|
async def create_user(*, user_in: UserIn): |
||||
|
user_saved = fake_save_user(user_in) |
||||
|
return user_saved |
||||
|
@ -1,25 +1,37 @@ |
|||||
from fastapi import Body, FastAPI, Path, Query |
from fastapi import Body, FastAPI, Path, Query |
||||
from starlette.status import HTTP_201_CREATED |
|
||||
from pydantic import BaseModel |
from pydantic import BaseModel |
||||
from pydantic.types import UrlStr |
from pydantic.types import EmailStr |
||||
from typing import Set, List |
from typing import Set, List |
||||
|
|
||||
app = FastAPI() |
app = FastAPI() |
||||
|
|
||||
|
class UserBase(BaseModel): |
||||
|
username: str |
||||
|
email: EmailStr |
||||
|
full_name: str = None |
||||
|
|
||||
class Item(BaseModel): |
class UserIn(UserBase): |
||||
name: str |
password: str |
||||
description: str = None |
|
||||
price: float |
|
||||
tax: float = None |
|
||||
tags: Set[str] = [] |
|
||||
|
|
||||
|
|
||||
@app.post( |
class UserOut(UserBase): |
||||
"/items/", |
pass |
||||
response_model=Item, |
|
||||
summary="Create an item", |
class UserInDB(UserBase): |
||||
description="Create an item with all the information, name, description, price, tax and a set of unique tags", |
hashed_password: str |
||||
) |
|
||||
async def create_item(*, item: Item): |
|
||||
return item |
def fake_password_hasher(raw_password: str): |
||||
|
return "supersecret" + raw_password |
||||
|
|
||||
|
|
||||
|
def fake_save_user(user_in: UserIn): |
||||
|
hashed_password = fake_password_hasher(user_in.password) |
||||
|
user_in_db = UserInDB(**user_in.dict(), hashed_password=hashed_password) |
||||
|
print("User saved! ..not really") |
||||
|
return user_in_db |
||||
|
|
||||
|
@app.post("/user/", response_model=UserOut) |
||||
|
async def create_user(*, user_in: UserIn): |
||||
|
user_saved = fake_save_user(user_in) |
||||
|
return user_saved |
||||
|
@ -1,29 +1,11 @@ |
|||||
from fastapi import Body, FastAPI, Path, Query |
from fastapi import Body, FastAPI, Path, Query, Form |
||||
from starlette.status import HTTP_201_CREATED |
|
||||
from pydantic import BaseModel |
from pydantic import BaseModel |
||||
from pydantic.types import UrlStr |
from pydantic.types import EmailStr |
||||
from typing import Set, List |
from typing import Set, List |
||||
|
|
||||
app = FastAPI() |
app = FastAPI() |
||||
|
|
||||
|
|
||||
class Item(BaseModel): |
@app.post("/login/") |
||||
name: str |
async def login(*, username: str = Form(...), password: str = Form(...)): |
||||
description: str = None |
return {"username": username} |
||||
price: float |
|
||||
tax: float = None |
|
||||
tags: Set[str] = [] |
|
||||
|
|
||||
|
|
||||
@app.post("/items/", response_model=Item, summary="Create an item") |
|
||||
async def create_item(*, item: Item): |
|
||||
""" |
|
||||
Create an item with all the information: |
|
||||
|
|
||||
* name: each item must have a name |
|
||||
* description: a long description |
|
||||
* price: required |
|
||||
* tax: if the item doesn't have tax, you can omit this |
|
||||
* tags: a set of unique tag strings for this item |
|
||||
""" |
|
||||
return item |
|
||||
|
@ -1,29 +1,11 @@ |
|||||
from fastapi import Body, FastAPI, Path, Query |
from fastapi import Body, FastAPI, Path, Query, File |
||||
from starlette.status import HTTP_201_CREATED |
|
||||
from pydantic import BaseModel |
from pydantic import BaseModel |
||||
from pydantic.types import UrlStr |
from pydantic.types import EmailStr |
||||
from typing import Set, List |
from typing import Set, List |
||||
|
|
||||
app = FastAPI() |
app = FastAPI() |
||||
|
|
||||
|
|
||||
class Item(BaseModel): |
@app.post("/files/") |
||||
name: str |
async def create_file(*, file: bytes = File(...)): |
||||
description: str = None |
return {"file_size": len(file)} |
||||
price: float |
|
||||
tax: float = None |
|
||||
tags: Set[str] = [] |
|
||||
|
|
||||
|
|
||||
@app.post("/items/", response_model=Item, summary="Create an item", response_description="The created item") |
|
||||
async def create_item(*, item: Item): |
|
||||
""" |
|
||||
Create an item with all the information: |
|
||||
|
|
||||
* name: each item must have a name |
|
||||
* description: a long description |
|
||||
* price: required |
|
||||
* tax: if the item doesn't have tax, you can omit this |
|
||||
* tags: a set of unique tag strings for this item |
|
||||
""" |
|
||||
return item |
|
||||
|
@ -1,13 +1,11 @@ |
|||||
from typing import List, Set |
from fastapi import Body, FastAPI, Path, Query, File, Form |
||||
|
|
||||
from fastapi import Body, FastAPI, Path, Query |
|
||||
from pydantic import BaseModel |
from pydantic import BaseModel |
||||
from pydantic.types import UrlStr |
from pydantic.types import EmailStr |
||||
from starlette.status import HTTP_201_CREATED |
from typing import Set, List |
||||
|
|
||||
app = FastAPI() |
app = FastAPI() |
||||
|
|
||||
|
|
||||
@app.get("/items/", deprecated=True) |
@app.post("/files/") |
||||
async def read_items(): |
async def create_file(*, file: bytes = File(...), token: str = Form(...)): |
||||
return [{"item_id": "Foo"}] |
return {"file_size": len(file), "token": token} |
||||
|
@ -1,13 +1,20 @@ |
|||||
from typing import List, Set |
|
||||
|
|
||||
from fastapi import Body, FastAPI, Path, Query |
from fastapi import Body, FastAPI, Path, Query |
||||
|
from starlette.status import HTTP_201_CREATED |
||||
from pydantic import BaseModel |
from pydantic import BaseModel |
||||
from pydantic.types import UrlStr |
from pydantic.types import UrlStr |
||||
from starlette.status import HTTP_201_CREATED |
from typing import Set, List |
||||
|
|
||||
app = FastAPI() |
app = FastAPI() |
||||
|
|
||||
|
|
||||
@app.get("/items/", operation_id="some_specific_id_you_define") |
class Item(BaseModel): |
||||
async def read_items(): |
name: str |
||||
return [{"item_id": "Foo"}] |
description: str = None |
||||
|
price: float |
||||
|
tax: float = None |
||||
|
tags: Set[str] = [] |
||||
|
|
||||
|
|
||||
|
@app.post("/items/", response_model=Item, status_code=HTTP_201_CREATED) |
||||
|
async def create_item(*, item: Item): |
||||
|
return item |
||||
|
@ -1,13 +1,20 @@ |
|||||
from typing import List, Set |
|
||||
|
|
||||
from fastapi import Body, FastAPI, Path, Query |
from fastapi import Body, FastAPI, Path, Query |
||||
|
from starlette.status import HTTP_201_CREATED |
||||
from pydantic import BaseModel |
from pydantic import BaseModel |
||||
from pydantic.types import UrlStr |
from pydantic.types import UrlStr |
||||
from starlette.status import HTTP_201_CREATED |
from typing import Set, List |
||||
|
|
||||
app = FastAPI() |
app = FastAPI() |
||||
|
|
||||
|
|
||||
@app.get("/items/", include_in_schema=False) |
class Item(BaseModel): |
||||
async def read_items(): |
name: str |
||||
return [{"item_id": "Foo"}] |
description: str = None |
||||
|
price: float |
||||
|
tax: float = None |
||||
|
tags: Set[str] = [] |
||||
|
|
||||
|
|
||||
|
@app.post("/items/", response_model=Item, tags=["items"]) |
||||
|
async def create_item(*, item: Item): |
||||
|
return item |
||||
|
@ -1,14 +1,25 @@ |
|||||
from typing import List, Set |
|
||||
|
|
||||
from fastapi import Body, FastAPI, Path, Query |
from fastapi import Body, FastAPI, Path, Query |
||||
|
from starlette.status import HTTP_201_CREATED |
||||
from pydantic import BaseModel |
from pydantic import BaseModel |
||||
from pydantic.types import UrlStr |
from pydantic.types import UrlStr |
||||
from starlette.status import HTTP_201_CREATED |
from typing import Set, List |
||||
from starlette.responses import UJSONResponse |
|
||||
|
|
||||
app = FastAPI() |
app = FastAPI() |
||||
|
|
||||
|
|
||||
@app.get("/items/", content_type=UJSONResponse) |
class Item(BaseModel): |
||||
async def read_items(): |
name: str |
||||
return [{"item_id": "Foo"}] |
description: str = None |
||||
|
price: float |
||||
|
tax: float = None |
||||
|
tags: Set[str] = [] |
||||
|
|
||||
|
|
||||
|
@app.post( |
||||
|
"/items/", |
||||
|
response_model=Item, |
||||
|
summary="Create an item", |
||||
|
description="Create an item with all the information, name, description, price, tax and a set of unique tags", |
||||
|
) |
||||
|
async def create_item(*, item: Item): |
||||
|
return item |
||||
|
@ -1,23 +1,29 @@ |
|||||
from typing import List, Set |
|
||||
|
|
||||
from fastapi import Body, FastAPI, Path, Query |
from fastapi import Body, FastAPI, Path, Query |
||||
|
from starlette.status import HTTP_201_CREATED |
||||
from pydantic import BaseModel |
from pydantic import BaseModel |
||||
from pydantic.types import UrlStr |
from pydantic.types import UrlStr |
||||
from starlette.status import HTTP_201_CREATED |
from typing import Set, List |
||||
from starlette.responses import HTMLResponse |
|
||||
|
|
||||
app = FastAPI() |
app = FastAPI() |
||||
|
|
||||
|
|
||||
@app.get("/items/", content_type=HTMLResponse) |
class Item(BaseModel): |
||||
async def read_items(): |
name: str |
||||
return """ |
description: str = None |
||||
<html> |
price: float |
||||
<head> |
tax: float = None |
||||
<title>Some HTML in here</title> |
tags: Set[str] = [] |
||||
</head> |
|
||||
<body> |
|
||||
<h1>Look ma! HTML!</h1> |
@app.post("/items/", response_model=Item, summary="Create an item") |
||||
</body> |
async def create_item(*, item: Item): |
||||
</html> |
""" |
||||
|
Create an item with all the information: |
||||
|
|
||||
|
* name: each item must have a name |
||||
|
* description: a long description |
||||
|
* price: required |
||||
|
* tax: if the item doesn't have tax, you can omit this |
||||
|
* tags: a set of unique tag strings for this item |
||||
""" |
""" |
||||
|
return item |
||||
|
@ -0,0 +1,29 @@ |
|||||
|
from fastapi import Body, FastAPI, Path, Query |
||||
|
from starlette.status import HTTP_201_CREATED |
||||
|
from pydantic import BaseModel |
||||
|
from pydantic.types import UrlStr |
||||
|
from typing import Set, List |
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
|
||||
|
class Item(BaseModel): |
||||
|
name: str |
||||
|
description: str = None |
||||
|
price: float |
||||
|
tax: float = None |
||||
|
tags: Set[str] = [] |
||||
|
|
||||
|
|
||||
|
@app.post("/items/", response_model=Item, summary="Create an item", response_description="The created item") |
||||
|
async def create_item(*, item: Item): |
||||
|
""" |
||||
|
Create an item with all the information: |
||||
|
|
||||
|
* name: each item must have a name |
||||
|
* description: a long description |
||||
|
* price: required |
||||
|
* tax: if the item doesn't have tax, you can omit this |
||||
|
* tags: a set of unique tag strings for this item |
||||
|
""" |
||||
|
return item |
@ -1,59 +1,13 @@ |
|||||
from typing import List, Set |
from typing import List, Set |
||||
|
|
||||
from fastapi import Body, FastAPI, Path, Query, Depends, Cookie |
from fastapi import Body, FastAPI, Path, Query |
||||
from pydantic import BaseModel |
from pydantic import BaseModel |
||||
from pydantic.types import UrlStr |
from pydantic.types import UrlStr |
||||
from starlette.status import HTTP_201_CREATED |
from starlette.status import HTTP_201_CREATED |
||||
from starlette.responses import HTMLResponse |
|
||||
from random import choice |
|
||||
|
|
||||
from sqlalchemy import create_engine |
|
||||
from sqlalchemy.orm import scoped_session, sessionmaker |
|
||||
from sqlalchemy import Column, Integer, DateTime, String, Boolean, ForeignKey |
|
||||
from sqlalchemy.ext.declarative import declarative_base, declared_attr |
|
||||
|
|
||||
|
|
||||
# SQLAlchemy specific code, as with any other app |
|
||||
|
|
||||
|
|
||||
SQLALCHEMY_DATABASE_URI = "postgresql://user:password@postgresserver/db" |
|
||||
|
|
||||
# By creating this a CustomBase class and inheriting from it, your models will have |
|
||||
# automatic __tablename__ attributes. So you don't have to declare them. |
|
||||
# So, your models will behave very similarly to, for example, Flask-SQLAlchemy |
|
||||
|
|
||||
class CustomBase: |
|
||||
# Generate __tablename__ automatically |
|
||||
@declared_attr |
|
||||
def __tablename__(cls): |
|
||||
return cls.__name__.lower() |
|
||||
|
|
||||
|
|
||||
Base = declarative_base(cls=CustomBase) |
|
||||
|
|
||||
|
|
||||
class User(Base): |
|
||||
# Own properties |
|
||||
id = Column(Integer, primary_key=True, index=True) |
|
||||
email = Column(String, unique=True, index=True) |
|
||||
hashed_password = Column(String) |
|
||||
is_active = Column(Boolean(), default=True) |
|
||||
|
|
||||
|
|
||||
engine = create_engine(SQLALCHEMY_DATABASE_URI, convert_unicode=True) |
|
||||
db_session = scoped_session( |
|
||||
sessionmaker(autocommit=False, autoflush=False, bind=engine) |
|
||||
) |
|
||||
|
|
||||
|
|
||||
def get_user(username, db_session): |
|
||||
return db_session.query(User).filter(User.id == username).first() |
|
||||
|
|
||||
# FastAPI specific code |
|
||||
app = FastAPI() |
app = FastAPI() |
||||
|
|
||||
|
|
||||
@app.get("/users/{username}") |
@app.get("/items/", deprecated=True) |
||||
def read_user(username: str): |
async def read_items(): |
||||
user = get_user(username, db_session) |
return [{"item_id": "Foo"}] |
||||
return user |
|
||||
|
@ -1,66 +1,13 @@ |
|||||
from typing import List, Set |
from typing import List, Set |
||||
|
|
||||
from fastapi import Body, FastAPI, Path, Query, Depends, Cookie |
from fastapi import Body, FastAPI, Path, Query |
||||
from pydantic import BaseModel |
from pydantic import BaseModel |
||||
from pydantic.types import UrlStr |
from pydantic.types import UrlStr |
||||
from starlette.status import HTTP_201_CREATED |
from starlette.status import HTTP_201_CREATED |
||||
from starlette.responses import HTMLResponse |
|
||||
from random import choice |
|
||||
|
|
||||
from typing import List, Optional, Union |
|
||||
|
|
||||
from pydantic import BaseModel |
|
||||
|
|
||||
from app.models.config import USERPROFILE_DOC_TYPE |
|
||||
from app.models.role import RoleEnum |
|
||||
from couchbase.bucket import Bucket |
|
||||
from couchbase.cluster import Cluster, PasswordAuthenticator |
|
||||
from couchbase import LOCKMODE_WAIT |
|
||||
|
|
||||
|
|
||||
def get_bucket(): |
|
||||
cluster = Cluster("couchbase://couchbasehost:8091") |
|
||||
authenticator = PasswordAuthenticator("username", "password") |
|
||||
cluster.authenticate(authenticator) |
|
||||
bucket: Bucket = cluster.open_bucket("bucket_name", lockmode=LOCKMODE_WAIT) |
|
||||
return bucket |
|
||||
|
|
||||
|
|
||||
class User(BaseModel): |
|
||||
username: str |
|
||||
email: Optional[str] = None |
|
||||
full_name: Optional[str] = None |
|
||||
disabled: Optional[bool] = None |
|
||||
|
|
||||
|
|
||||
class UserInDB(User): |
|
||||
type: str = USERPROFILE_DOC_TYPE |
|
||||
hashed_password: str |
|
||||
|
|
||||
class Meta: |
|
||||
key: Optional[str] = None |
|
||||
|
|
||||
|
|
||||
def get_user_doc_id(username): |
|
||||
return f"userprofile::{username}" |
|
||||
|
|
||||
|
|
||||
def get_user(bucket: Bucket, username: str): |
|
||||
doc_id = get_user_doc_id(username) |
|
||||
result = bucket.get(doc_id, quiet=True) |
|
||||
if not result.value: |
|
||||
return None |
|
||||
user = UserInDB(**result.value) |
|
||||
user.Meta.key = result.key |
|
||||
return user |
|
||||
|
|
||||
|
|
||||
# FastAPI specific code |
|
||||
app = FastAPI() |
app = FastAPI() |
||||
|
|
||||
|
|
||||
@app.get("/users/{username}") |
@app.get("/items/", operation_id="some_specific_id_you_define") |
||||
def read_user(username: str): |
async def read_items(): |
||||
bucket = get_bucket() |
return [{"item_id": "Foo"}] |
||||
user = get_user(bucket=bucket, username=username) |
|
||||
return user |
|
||||
|
@ -0,0 +1,13 @@ |
|||||
|
from typing import List, Set |
||||
|
|
||||
|
from fastapi import Body, FastAPI, Path, Query |
||||
|
from pydantic import BaseModel |
||||
|
from pydantic.types import UrlStr |
||||
|
from starlette.status import HTTP_201_CREATED |
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
|
||||
|
@app.get("/items/", include_in_schema=False) |
||||
|
async def read_items(): |
||||
|
return [{"item_id": "Foo"}] |
@ -0,0 +1,14 @@ |
|||||
|
from typing import List, Set |
||||
|
|
||||
|
from fastapi import Body, FastAPI, Path, Query |
||||
|
from pydantic import BaseModel |
||||
|
from pydantic.types import UrlStr |
||||
|
from starlette.status import HTTP_201_CREATED |
||||
|
from starlette.responses import UJSONResponse |
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
|
||||
|
@app.get("/items/", content_type=UJSONResponse) |
||||
|
async def read_items(): |
||||
|
return [{"item_id": "Foo"}] |
@ -0,0 +1,23 @@ |
|||||
|
from typing import List, Set |
||||
|
|
||||
|
from fastapi import Body, FastAPI, Path, Query |
||||
|
from pydantic import BaseModel |
||||
|
from pydantic.types import UrlStr |
||||
|
from starlette.status import HTTP_201_CREATED |
||||
|
from starlette.responses import HTMLResponse |
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
|
||||
|
@app.get("/items/", content_type=HTMLResponse) |
||||
|
async def read_items(): |
||||
|
return """ |
||||
|
<html> |
||||
|
<head> |
||||
|
<title>Some HTML in here</title> |
||||
|
</head> |
||||
|
<body> |
||||
|
<h1>Look ma! HTML!</h1> |
||||
|
</body> |
||||
|
</html> |
||||
|
""" |
@ -0,0 +1,59 @@ |
|||||
|
from typing import List, Set |
||||
|
|
||||
|
from fastapi import Body, FastAPI, Path, Query, Depends, Cookie |
||||
|
from pydantic import BaseModel |
||||
|
from pydantic.types import UrlStr |
||||
|
from starlette.status import HTTP_201_CREATED |
||||
|
from starlette.responses import HTMLResponse |
||||
|
from random import choice |
||||
|
|
||||
|
from sqlalchemy import create_engine |
||||
|
from sqlalchemy.orm import scoped_session, sessionmaker |
||||
|
from sqlalchemy import Column, Integer, DateTime, String, Boolean, ForeignKey |
||||
|
from sqlalchemy.ext.declarative import declarative_base, declared_attr |
||||
|
|
||||
|
|
||||
|
# SQLAlchemy specific code, as with any other app |
||||
|
|
||||
|
|
||||
|
SQLALCHEMY_DATABASE_URI = "postgresql://user:password@postgresserver/db" |
||||
|
|
||||
|
# By creating this a CustomBase class and inheriting from it, your models will have |
||||
|
# automatic __tablename__ attributes. So you don't have to declare them. |
||||
|
# So, your models will behave very similarly to, for example, Flask-SQLAlchemy |
||||
|
|
||||
|
class CustomBase: |
||||
|
# Generate __tablename__ automatically |
||||
|
@declared_attr |
||||
|
def __tablename__(cls): |
||||
|
return cls.__name__.lower() |
||||
|
|
||||
|
|
||||
|
Base = declarative_base(cls=CustomBase) |
||||
|
|
||||
|
|
||||
|
class User(Base): |
||||
|
# Own properties |
||||
|
id = Column(Integer, primary_key=True, index=True) |
||||
|
email = Column(String, unique=True, index=True) |
||||
|
hashed_password = Column(String) |
||||
|
is_active = Column(Boolean(), default=True) |
||||
|
|
||||
|
|
||||
|
engine = create_engine(SQLALCHEMY_DATABASE_URI, convert_unicode=True) |
||||
|
db_session = scoped_session( |
||||
|
sessionmaker(autocommit=False, autoflush=False, bind=engine) |
||||
|
) |
||||
|
|
||||
|
|
||||
|
def get_user(username, db_session): |
||||
|
return db_session.query(User).filter(User.id == username).first() |
||||
|
|
||||
|
# FastAPI specific code |
||||
|
app = FastAPI() |
||||
|
|
||||
|
|
||||
|
@app.get("/users/{username}") |
||||
|
def read_user(username: str): |
||||
|
user = get_user(username, db_session) |
||||
|
return user |
@ -0,0 +1,66 @@ |
|||||
|
from typing import List, Set |
||||
|
|
||||
|
from fastapi import Body, FastAPI, Path, Query, Depends, Cookie |
||||
|
from pydantic import BaseModel |
||||
|
from pydantic.types import UrlStr |
||||
|
from starlette.status import HTTP_201_CREATED |
||||
|
from starlette.responses import HTMLResponse |
||||
|
from random import choice |
||||
|
|
||||
|
from typing import List, Optional, Union |
||||
|
|
||||
|
from pydantic import BaseModel |
||||
|
|
||||
|
from app.models.config import USERPROFILE_DOC_TYPE |
||||
|
from app.models.role import RoleEnum |
||||
|
from couchbase.bucket import Bucket |
||||
|
from couchbase.cluster import Cluster, PasswordAuthenticator |
||||
|
from couchbase import LOCKMODE_WAIT |
||||
|
|
||||
|
|
||||
|
def get_bucket(): |
||||
|
cluster = Cluster("couchbase://couchbasehost:8091") |
||||
|
authenticator = PasswordAuthenticator("username", "password") |
||||
|
cluster.authenticate(authenticator) |
||||
|
bucket: Bucket = cluster.open_bucket("bucket_name", lockmode=LOCKMODE_WAIT) |
||||
|
return bucket |
||||
|
|
||||
|
|
||||
|
class User(BaseModel): |
||||
|
username: str |
||||
|
email: Optional[str] = None |
||||
|
full_name: Optional[str] = None |
||||
|
disabled: Optional[bool] = None |
||||
|
|
||||
|
|
||||
|
class UserInDB(User): |
||||
|
type: str = USERPROFILE_DOC_TYPE |
||||
|
hashed_password: str |
||||
|
|
||||
|
class Meta: |
||||
|
key: Optional[str] = None |
||||
|
|
||||
|
|
||||
|
def get_user_doc_id(username): |
||||
|
return f"userprofile::{username}" |
||||
|
|
||||
|
|
||||
|
def get_user(bucket: Bucket, username: str): |
||||
|
doc_id = get_user_doc_id(username) |
||||
|
result = bucket.get(doc_id, quiet=True) |
||||
|
if not result.value: |
||||
|
return None |
||||
|
user = UserInDB(**result.value) |
||||
|
user.Meta.key = result.key |
||||
|
return user |
||||
|
|
||||
|
|
||||
|
# FastAPI specific code |
||||
|
app = FastAPI() |
||||
|
|
||||
|
|
||||
|
@app.get("/users/{username}") |
||||
|
def read_user(username: str): |
||||
|
bucket = get_bucket() |
||||
|
user = get_user(bucket=bucket, username=username) |
||||
|
return user |
Loading…
Reference in new issue