目录
(2)response_model_exclude_unset
一、简介
fastapi
,一个用于构建 API 的现代、快速(高性能)的web框架。
fastapi
是建立在Starlette和Pydantic基础上的,Pydantic是一个基于Python类型提示来定义数据验证、序列化和文档的库。Starlette是一种轻量级的ASGI框架/工具包,是构建高性能Asyncio服务的理性选择。
快速:可与 NodeJS 和 Go 比肩的
极高性能
(归功于Starlette
和Pydantic
),是最快的 Python web 框架之一。高效编码:提高功能开发速度约 200% 至 300%。
更少bug:减少约 40% 的人为(开发者)导致错误。
智能:极佳的编辑器支持。处处皆可自动补全,减少调试时间。
简单:设计的
易于使用和学习
,阅读文档的时间更短。简短:使代码重复最小化。通过不同的参数声明实现丰富功能。
健壮:生产可用级别的代码。还有
自动生成的交互式文档
。
依赖:Python 3.6 及更高版本,FastAPI 站在以下巨人的肩膀之上。
[Starlette](https://www.starlette.io/) 负责 web 部分(Asyncio)
[Pydantic](https://pydantic-docs.helpmanual.io/) 负责数据部分(类型提示)
FastApi是站在前人肩膀上,集成了多种框架的优点的新秀框架。它出现的比较晚,2018年底才发布在github上。广泛应用于当前各种前后端分离的项目开发,测试运维自动化以及微服务的场景中。
二、quick start
2.1 简单案例
安装依赖
pip install fastapi
你还会需要一个 ASGI 服务器,生产环境可以使用 Uvicorn
pip install uvicorn
编写代码
from fastapi import FastAPI # FastAPI 是一个为你的 API 提供了所有功能的 Python 类。
app = FastAPI() # 这个实例将是创建你所有 API 的主要交互对象。这个 app 同样在如下命令中被 uvicorn 所引用
@app.get("/")
async def root():
return {"message": "Hello clay"}
运行服务器
通过以下命令运行服务器:
uvicorn quickstart:app --reload
quickstart是文件名,app是代码中app = FastAPI()的名字。
也可以直接运行:
在你quickstart文件中写入如下代码:
if __name__ == '__main__':
import uvicorn
uvicorn.run("quickstart:app", host="127.0.0.1", port=8080, reload=True)
右键run运行即可。
测试
当我们浏览器访问http://127.0.0.1:8000/,就会获取数据。
2.2 交互式 API 文档
fastapi有着非常棒的交互式 API 文档,这一点很吸引人。
跳转到 http://127.0.0.1:8000/docs。你将会看到自动生成的交互式 API 文档。
三、路径操作
3.1 路径操作装饰器
fastapi支持各种请求方式:
@app.get()
@app.post()
@app.put()
@app.patch()
@app.delete()
@app.options()
@app.head()
@app.trace()
from fastapi import FastAPI
app = FastAPI()
@app.get("/get")
def get_test():
return {"method": "get方法"}
@app.post("/post")
def post_test():
return {"method": "post方法"}
@app.put("/put")
def put_test():
return {"method": "put方法"}
@app.delete("/delete")
def delete_test():
return {"method": "delete方法"}
if __name__ == '__main__':
import uvicorn
uvicorn.run("quickstart:app", host="127.0.0.1", port=8080, reload=True)
路径操作装饰器参数:
@app.post(
"/items/{item_id}",
# response_model=Item, # 响应的数据模型
# status_code=status.HTTP_200_OK, # 响应的状态码
tags=["商品"], # 接口分组名
summary="this is summary", # 接口的描述简介
description="this is description", # 接口的描述详情
response_description= "this is response_description", # 接口的响应描述详情
deprecated=False, # 接口是否废弃了,如果True,API文档会变灰
)
3.2 include_router
我们在真实web项目中,业务很复杂,一定会用到路由分发。
main.py
from typing import Union
from fastapi import FastAPI
import uvicorn
from shop.apps.app01.urls import shop
from shop.apps.app02.urls import user
app = FastAPI()
app.include_router(shop, prefix="/shop", tags=["商城接口", ])
app.include_router(user, prefix="/user", tags=["用户中心接口", ])
if __name__ == '__main__':
uvicorn.run("main:app", host="127.0.0.1", port=8080, reload=True)
app01/urls
from fastapi import APIRouter
shop = APIRouter()
@shop.get("/food")
def shop_food():
return {"shop": "food"}
@shop.get("/bed")
def shop_food():
return {"shop": "bed"}
app02/urls
from fastapi import APIRouter
user = APIRouter()
@user.post("/login")
def user_login():
return {"user": "login"}
@user.post("/reg")
def user_reg():
return {"user": "reg"}
四、请求与响应
4.1 路径参数
(1)基本用法
以使用与 Python 格式化字符串相同的语法来声明路径"参数"或"变量":
@app.get("/user/{user_id}")
def get_user(user_id):
print(user_id, type(user_id))
return {"user_id": user_id}
路径参数 user_id
的值将作为参数 user_id
传递给你的函数。
(2)有类型的路径参数
你可以使用标准的 Python 类型标注为函数中的路径参数声明类型。
@app.get("/user/{user_id}")
def get_user(user_id: int): # 这样写参数可以将传来的user_id自动转为int类型
print(user_id, type(user_id))
return {"user_id": user_id}
(3)注意顺序
在创建路径操作时,你会发现有些情况下路径是固定的。
比如 /users/me
,我们假设它用来获取关于当前用户的数据.
然后,你还可以使用路径 /user/{username}
来通过用户名 获取关于特定用户的数据。
由于路径操作是按顺序依次运行的,你需要确保路径 /user/me
声明在路径 /user/{username}
之前:
@app.get("/user/me")
async def read_user_me():
return {"username": "the current user"}
@app.get("/user/{username}")
async def read_user(username: str):
return {"username": username}
否则,/user/{username}
的路径还将与 /user/me
相匹配,"认为"自己正在接收一个值为 "me"
的 username
参数。
4.2 查询参数(请求参数)
路径函数中声明不属于路径参数的其他函数参数时,它们将被自动解释为"查询字符串"参数,就是 url? 之后用&
分割的 key-value 键值对。
@app.get("/jobs")
async def search_jobs(kd: str, city: Union[str, None] = None, xl: Union[str, None] = None): # 有默认值即可选,否则必选
if city or xl:
return {"kd": kd, "city": city, "xl": xl}
return {"kd": kd}
在这个例子中,函数参数 city
和xl
是可选的,并且默认值为 None
。
自python3.5开始,PEP484为python引入了类型注解(type hints),typing的主要作用有:
类型检查,防止运行时出现参数、返回值类型不符。
作为开发文档附加说明,方便使用者调用时传入和返回参数类型。
模块加入不会影响程序的运行不会报正式的错误,pycharm支持typing检查错误时会出现黄色警告。
type hints
主要是要指示函数的输入和输出的数据类型,数据类型在typing 包中,基本类型有str list dict等等,
Union 是当有多种可能的数据类型时使用,比如函数有可能根据不同情况有时返回str或返回list,那么就可以写成Union[list, str] Optional 是Union的一个简化, 当 数据类型中有可能是None时,比如有可能是str也有可能是None,则Optional[str], 相当于Union[str, None]。
4.3 请求体数据
当你需要将数据从客户端(例如浏览器)发送给 API 时,你将其作为「请求体」发送。请求体是客户端发送给 API 的数据。响应体是 API 发送给客户端的数据。
FastAPI 基于 Pydantic
,Pydantic
主要用来做类型强制检查(校验数据)。不符合类型要求就会抛出异常。
对于 API 服务,支持类型检查非常有用,会让服务更加健壮,也会加快开发速度,因为开发者再也不用自己写一行一行的做类型检查。
安装上手
pip install pydantic
from typing import Union, List, Optional
from fastapi import FastAPI
from pydantic import BaseModel, Field, ValidationError, field_validator
import uvicorn
from datetime import date
class Addr(BaseModel):
province: str
city: str
class User(BaseModel):
name: str = 'root'
age: int = Field(default=0, lt=100, gt=0)
birth: Optional[date] = None
friends: List[int] = []
description: Union[str, None] = None
addr: Optional[Addr] = None # 类型嵌套
@field_validator('name')
def name_must_alpha(cls, v): # 校验name必须是字母的组合
if not v.isalpha():
raise ValueError('name must be alpha')
return v
class Data(BaseModel): # 类型嵌套
users: List[User]
app = FastAPI()
@app.post("/data/")
async def create_data(data: Data): # 请求体参数必须符合Data类的格式
# 添加数据库
return data
if __name__ == '__main__':
try:
uvicorn.run("test:app", host="127.0.0.1", port=8080, reload=True)
except ValidationError as e:
print(e.json())
和声明查询参数时一样,当一个模型属性具有默认值时,它不是必需的。否则它是一个必需属性。将默认值设为 None
可使其成为可选属性。
FastAPI 会自动将定义的模型类转化为JSON Schema
,Schema 成为 OpenAPI 生成模式的一部分,并显示在 API 交互文档中,查看 API 交互文档如下,该接口将接收application/json
类型的参数。
FastAPI 支持同时定义 Path 参数、Query 参数和请求体参数,FastAPI 将会正确识别并获取数据。
参数在 url 中也声明了,它将被解释为 path 参数
参数是单一类型(例如int、float、str、bool等),它将被解释为 query 参数
参数类型为继承 Pydantic 模块的
BaseModel
类的数据模型类,则它将被解释为请求体参
4.4 form表单数据
在 OAuth2 规范的一种使用方式(密码流)中,需要将用户名、密码作为表单字段发送,而不是 JSON。
FastAPI 可以使用Form组件来接收表单数据,需要先使用pip install python-multipart
命令进行安装。
pip install python-multipart
import uvicorn
from fastapi import FastAPI, Form
app = FastAPI()
# 用户注册功能
@app.post("/regin")
def regin(username: str = Form(..., max_length=16, min_length=8, regex='[a-zA-Z]'),
password: str = Form(..., max_length=16, min_length=8, regex='[0-9]')):
print(f"username:{username},password:{password}")
return {"username": username}
4.5 文件上传
import uvicorn
from fastapi import FastAPI, File, UploadFile
from typing import List
app = FastAPI()
# file: bytes = File():适合小文件上传
@app.post("/files/")
async def create_file(file: bytes = File()):
print("file:", file)
return {"file_size": len(file)}
# 上传多个文件
@app.post("/multiFiles/")
async def create_files(files: List[bytes] = File()):
return {"file_sizes": [len(file) for file in files]}
# file: UploadFile:适合大文件上传
@app.post("/uploadFile/")
async def create_upload_file(file: UploadFile):
with open(f"{file.filename}", 'wb') as f:
for chunk in iter(lambda: file.file.read(1024), b''):
f.write(chunk)
return {"filename": file.filename}
@app.post("/multiUploadFiles/")
async def create_upload_files(files: List[UploadFile]):
return {"filenames": [file.filename for file in files]}
if __name__ == '__main__':
uvicorn.run("test:app", host="127.0.0.1", port=8080, reload=True)
4.6 Reqeust对象
有些情况下我们希望能直接访问Request对象。例如我们在路径操作函数中想获取客户端的IP地址,需要在函数中声明Request类型的参数,FastAPI 就会自动传递 Request 对象给这个参数,我们就可以获取到 Request 对象及其属性信息,例如 header、url、cookie、session 等。
from urllib.request import Request
import uvicorn
from fastapi import FastAPI, Request
app = FastAPI()
@app.get("/items")
async def items(request: Request):
return {
"请求URL:": request.url,
"请求ip:": request.client.host,
"请求宿主:": request.headers.get("user-agent"),
"cookies": request.cookies,
}
if __name__ == '__main__':
uvicorn.run("test:app", host="127.0.0.1", port=8080, reload=True)
4.7 请求静态文件
在 Web 开发中,需要请求很多静态资源文件(不是由服务器生成的文件),如 css/js 和图片文件等。
import uvicorn
from fastapi import FastAPI, Request
from starlette.staticfiles import StaticFiles
app = FastAPI()
app.mount("/static", StaticFiles(directory="static"))
if __name__ == '__main__':
uvicorn.run("test:app", host="127.0.0.1", port=8080, reload=True)
当我访问http://127.0.0.1:8080/static/1.jpg就会获取到静态资源图片
4.8 响应模型相关参数
(1)response_model
前面写的这么多路径函数最终 return 的都是自定义结构的字典,FastAPI 提供了 response_model 参数,声明 return 响应体的模型。
# 路径操作
@app.post("/items/", response_model=Item)
# 路径函数
async def create_item(item: Item):
...
response_model 是路径操作的参数,并不是路径函数的参数哦。
FastAPI将使用response_model
进行以下操作:
-
将输出数据转换为response_model中声明的数据类型。
-
验证数据结构和类型
-
将输出数据限制为该model定义的
-
添加到OpenAPI中
-
在自动文档系统中使用。
你可以在任意的路径操作中使用 response_model
参数来声明用于响应的模型
案例:
-
注册功能
-
输入账号、密码、昵称、邮箱,注册成功后返回个人信息
from typing import Union
import uvicorn
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserIn(BaseModel):
username: str
password: str
email: EmailStr
full_name: Union[str, None] = None
class UserOut(BaseModel):
username: str
email: EmailStr
full_name: Union[str, None] = None
@app.post("/user/", response_model=UserOut)
async def create_user(user: UserIn):
return user
if __name__ == '__main__':
uvicorn.run("test:app", host="127.0.0.1", port=8080, reload=True)
(2)response_model_exclude_unset
通过上面的例子,我们学到了如何用response_model控制响应体结构,但是如果它们实际上没有存储,则可能要从结果中忽略它们。例如,如果model在NoSQL数据库中具有很多可选属性,但是不想发送很长的JSON响应,其中包含默认值。
案例:
from typing import List, Union
import uvicorn
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: float = 10.5
tags: List[str] = []
items = {
"foo": {"name": "Foo", "price": 50.2},
"bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
"baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}
@app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True)
async def read_item(item_id: str):
return items[item_id]
if __name__ == '__main__':
uvicorn.run("test:app", host="127.0.0.1", port=8080, reload=True)
注意:items是模拟数据库。
这段代码的意思是当我们传item_id参数,比如foo,只有name和price两个key,如果我们不设置response_model_exclude_unset,那么会返回Item实体类所有的key,但如果我们设置了,返回的只有name和price。
不设置unset参数:
{
"name": "Foo",
"description": null,
"price": 50.2,
"tax": 10.5,
"tags": []
}
设置unset参数:
{
"name": "Foo",
"price": 50.2
}
使用路径操作装饰器的 response_model
参数来定义响应模型,特别是确保私有数据被过滤掉。使用 response_model_exclude_unset
来仅返回显式设定的值。 除了response_model_exclude_unset
以外,还有response_model_exclude_defaults
和response_model_exclude_none
,我们可以很直观的了解到他们的意思,不返回是默认值的字段和不返回是None的字段。
(3)INCLUDE和EXCLUDE
# response_model_exclude
@app.get("/items/{item_id}", response_model=Item, response_model_exclude={"description"}, )
async def read_item(item_id: str):
return items[item_id]
# response_model_include
@app.get("/items/{item_id}", response_model=Item, response_model_include={"name", "price"}, )
async def read_item(item_id: str):
return items[item_id]
很容易理解,就是排除某个字段和只接受某个字段。
五、ORM操作
在大型的web开发中,我们肯定会用到数据库操作,那么FastAPI也支持数据库的开发,你可以用 PostgreSQL、MySQL、 SQLite Oracle 等。本文用SQLite为例。我们看下在fastapi是如何操作设计数据库的。
fastapi是一个很优秀的框架,但是缺少一个合适的orm,官方代码里面使用的是sqlalchemy,Tortoise ORM 是受 Django 启发的易于使用的异步 ORM (对象关系映射器)。
Tortoise ORM 目前支持以下数据库:
PostgreSQL >= 9.4(使用
asyncpg
)SQLite(使用
aiosqlite
)MySQL/MariaDB(使用
aiomysql
或使用asyncmy)
安装依赖:
pip install tortoise-orm
pip install aiomysql
5.1 创建模型
以选课系统为例:
models.py
from tortoise.models import Model
from tortoise import fields
class Clas(Model):
name = fields.CharField(max_length=255, description='班级名称')
class Teacher(Model):
id = fields.IntField(pk=True)
name = fields.CharField(max_length=255, description='姓名')
tno = fields.IntField(description='账号')
pwd = fields.CharField(max_length=255, description='密码')
class Student(Model):
id = fields.IntField(pk=True)
sno = fields.IntField(description='学号')
pwd = fields.CharField(max_length=255, description='密码')
name = fields.CharField(max_length=255, description='姓名')
# 一对多
clas = fields.ForeignKeyField('models.Clas', related_name='students')
# 多对多
courses = fields.ManyToManyField('models.Course', related_name='students',description='学生选课表')
class Course(Model):
id = fields.IntField(pk=True)
name = fields.CharField(max_length=255, description='课程名')
teacher = fields.ForeignKeyField('models.Teacher', related_name='courses', description='课程讲师')
6.2、aerich迁移工具
main.py
import uvicorn
from fastapi import FastAPI
from tortoise.contrib.fastapi import register_tortoise
from settings import TORTOISE_ORM
app = FastAPI()
# 该方法会在fastapi启动时触发,内部通过传递进去的app对象,监听服务启动和终止事件
# 当检测到启动事件时,会初始化Tortoise对象,如果generate_schemas为True则还会进行数据库迁移
# 当检测到终止事件时,会关闭连接
register_tortoise(
app,
config=TORTOISE_ORM,
# generate_schemas=True, # 如果数据库为空,则自动生成对应表单,生产环境不要开
# add_exception_handlers=True, # 生产环境不要开,会泄露调试信息
)
if __name__ == '__main__':
uvicorn.run('main:app', host='127.0.0.1', port=8000, reload=True,
debug=True, workers=1)
settings.py
TORTOISE_ORM = {
'connections': {
'default': {
# 'engine': 'tortoise.backends.asyncpg', PostgreSQL
'engine': 'tortoise.backends.mysql', # MySQL or Mariadb
'credentials': {
'host': '127.0.0.1',
'port': '3306',
'user': 'root',
'password': 'root',
'database': 'fastapi',
'minsize': 1,
'maxsize': 5,
'charset': 'utf8mb4',
"echo": True
}
},
},
'apps': {
'models': {
'models': ['models', 'aerich.models'],
'default_connection': 'default',
}
},
'use_tz': False,
'timezone': 'Asia/Shanghai'
}
aerich
是一种ORM迁移工具,需要结合tortoise
异步orm框架使用。安装aerich
pip install aerich
1. 初始化配置,只需要使用一次
aerich init -t settings.TORTOISE_ORM # TORTOISE_ORM配置的位置)
初始化完会在当前目录生成一个文件:pyproject.toml和一个文件夹:migrations
pyproject.toml
:保存配置文件路径,低版本可能是aerich.ini
migrations
:存放迁移文件
2. 初始化数据库,一般情况下只用一次
aerich init-db
此时数据库中就有相应的表格
如果
TORTOISE_ORM
配置文件中的models
改了名,则执行这条命令时需要增加--app
参数,来指定你修改的名字
3. 更新模型并进行迁移
修改model类,重新生成迁移文件,比如添加一个字段
aerich migrate [--name] (标记修改操作) # aerich migrate --name add_column
迁移文件名的格式为 {version_num}{datetime}{name|update}.json。
注意,此时sql并没有执行,数据库中表中没有addr字段。
4. 重新执行迁移,写入数据库
aerich upgrade
5. 回到上一个版本
aerich downgrade
6. 查看历史迁移记录
aerich history
6.3 选课系统接口开发
api/student.py
from fastapi.exceptions import HTTPException
from models import *
from pydantic import BaseModel
from typing import List, Union
from fastapi import APIRouter
api_student = APIRouter()
@api_student.get("/student")
async def getAllStudent():
students = await Student.all().values("name", "clas__name")
# students = await Student.filter(name__icontains='a').values("name", "clas__name")
# print("students", students)
# for i in students:
# print(i)
#
# rain = await Student.get(name='rain')
# print(rain, type(rain))
# print(rain.sno)
return students
class StudentModel(BaseModel):
name: str
pwd: str
sno: int
clas_id: Union[int, None] = None
courses: List[int] = []
@api_student.post("/student")
async def addStudent(stu: StudentModel):
# 添加数据库操作
# 方式1
# student = Student(name=stu.name, pwd=stu.pwd, sno=stu.sno, clas_id=stu.clas)
# await student.save()
# 方式2
student = await Student.create(name=stu.name, pwd=stu.pwd, sno=stu.sno, clas_id=stu.clas_id)
print(student, dir(student))
# 添加多对多关系记录
courses = await Course.filter(id__in=stu.courses)
print("courses", courses)
await student.courses.add(*courses)
print("student", student.courses)
return student
@api_student.put("/student/{student_id}")
async def update_student(student_id: int, student: StudentModel):
data = student.dict(exclude_unset=True)
courses = data.pop("courses")
print(data, courses)
await Student.filter(id=student_id).update(**data)
courses = await Course.filter(id__in=student.courses)
edit_student = await Student.get(id=student_id)
await edit_student.courses.clear()
await edit_student.courses.add(*courses)
return student
@api_student.delete("/student/{student_id}")
async def delete_student(student_id: int):
deleted_count = await Student.filter(id=student_id).delete() # 条件删除
if not deleted_count:
raise HTTPException(status_code=404, detail=f"Student {student_id} not found")
return {}
六、中间件与CORS
6.1 中间件
你可以向 FastAPI 应用添加中间件.
"中间件"是一个函数,它在每个请求被特定的路径操作处理之前,以及在每个响应之后工作.
如果你使用了
yield
关键字依赖, 依赖中的退出代码将在执行中间件后执行。如果有任何后台任务(稍后记录), 它们将在执行中间件后运行。
要创建中间件你可以在函数的顶部使用装饰器 @app.middleware("http")
.
中间件参数接收如下参数:
request
一个函数
call_next
,它将接收request,作为参数。
这个函数将
request
传递给相应的 路径操作。然后它将返回由相应的路径操作生成的
response。
然后你可以在返回
response
前进一步修改它。
import uvicorn
from fastapi import FastAPI
from fastapi import Request
from fastapi.responses import Response
import time
app = FastAPI()
@app.middleware("http")
async def m2(request: Request, call_next):
# 请求代码块
print("m2 request")
response = await call_next(request)
# 响应代码块
response.headers["author"] = "yuan"
print("m2 response")
return response
@app.middleware("http")
async def m1(request: Request, call_next):
# 请求代码块
print("m1 request")
# if request.client.host in ["127.0.0.1", ]: # 黑名单
# return Response(content="visit forbidden")
# if request.url.path in ["/user"]:
# return Response(content="visit forbidden")
start = time.time()
response = await call_next(request)
# 响应代码块
print("m1 response")
end = time.time()
response.headers["ProcessTimer"] = str(end - start)
return response
@app.get("/user")
def get_user():
time.sleep(3)
print("get_user函数执行")
return {
"user": "current user"
}
@app.get("/item/{item_id}")
def get_item(item_id: int):
time.sleep(2)
print("get_item函数执行")
return {
"item_id": item_id
}
if __name__ == '__main__':
uvicorn.run('main:app', host='127.0.0.1', port=8030, reload=True,
debug=True, workers=1)
6.2 CORS
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
</head>
<body>
<p>click</p>
<script>
$("p").click(function () {
$.ajax({
url: "http://127.0.0.1:8080/",
success: function (res) {
$("p").html(res.message)
},
})
})
</script>
</body>
</html>
@app.middleware("http")
async def CORSMiddleware(request: Request, call_next):
response = await call_next(request)
print(response.headers)
return response
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
origins = [
"http://localhost:63342"
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins, # *:代表所有客户端
allow_credentials=True,
allow_methods=["GET"],
allow_headers=["*"],
)
@app.get("/")
def main():
return {"message": "Hello World"}
if __name__ == '__main__':
import uvicorn
uvicorn.run("main:app", host="127.0.0.1", port=8080, debug=True, reload=True)