v0.1 fastapi: route + model + orm
This commit is contained in:
@@ -0,0 +1 @@
|
||||
__pycache__
|
||||
Generated
+5
@@ -0,0 +1,5 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
Generated
+14
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module external.system.id="pyproject.toml" type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="~/arch/code_repo/infrastructure_system_softwares/language_dev/python/fastapi_app_dev/fastapi_tutor/.venv" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
<component name="PackageRequirementsSettings" />
|
||||
<component name="PyDocumentationSettings" />
|
||||
<component name="ReSTService" />
|
||||
<component name="TestRunnerService" />
|
||||
</module>
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
Generated
+4
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="~/arch/code_repo/infrastructure_system_softwares/language_dev/python/fastapi_app_dev/fastapi_tutor/.venv" project-jdk-type="Python SDK" />
|
||||
</project>
|
||||
Generated
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/fastapi-tutor.iml" filepath="$PROJECT_DIR$/.idea/fastapi-tutor.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,7 @@
|
||||
from typing import Annotated
|
||||
|
||||
|
||||
def say_hello(name: Annotated[str, "this is just metadata"]) -> str:
|
||||
return f"Hello {name}"
|
||||
|
||||
print(say_hello('Annotated'))
|
||||
@@ -0,0 +1,27 @@
|
||||
import pymysql.cursors
|
||||
|
||||
# Connect to the database
|
||||
connection = pymysql.connect(
|
||||
host="localhost",
|
||||
user="geeker",
|
||||
password="geeker",
|
||||
database="fastapi_tutor",
|
||||
cursorclass=pymysql.cursors.DictCursor,
|
||||
)
|
||||
|
||||
with connection:
|
||||
with connection.cursor() as cursor:
|
||||
# Create a new record
|
||||
sql = "INSERT INTO `users` (`email`, `password`) VALUES (%s, %s)"
|
||||
cursor.execute(sql, ("webmaster@python.org", "very-secret"))
|
||||
|
||||
# connection is not autocommit by default. So you must commit to save
|
||||
# your changes.
|
||||
connection.commit()
|
||||
|
||||
with connection.cursor() as cursor:
|
||||
# Read a single record
|
||||
sql = "SELECT `id`, `password` FROM `users` WHERE `email`=%s"
|
||||
cursor.execute(sql, ("webmaster@python.org",))
|
||||
result = cursor.fetchone()
|
||||
print(result)
|
||||
@@ -0,0 +1,4 @@
|
||||
id title content user_id created_at
|
||||
-- ---------- ------------- ------- --------------------------
|
||||
1 Alice 的第一篇 Hello world 1 2026-09-18 22:42:47.786259
|
||||
2 Alice 的第二篇 SQLAlchemy 真香 1 2026-09-18 22:42:48.010497
|
||||
@@ -0,0 +1,234 @@
|
||||
from sqlalchemy import create_engine, Column, Integer,String,DATETIME,DECIMAL, or_, and_,not_,func
|
||||
from sqlalchemy.orm import declarative_base, sessionmaker
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
db_url = "mysql+pymysql://geeker:geeker@localhost:3306/fastapi_tutor?charset=utf8mb4"
|
||||
|
||||
engine = create_engine(db_url, pool_size= 50, echo=True)
|
||||
|
||||
Base = declarative_base() # 获取基类!
|
||||
|
||||
class Order(Base): # python里的表名字
|
||||
__tablename__ = 'order_info_detail' # 实际数据库的表名字
|
||||
id = Column( Integer # 声明字段的数据类型
|
||||
, primary_key=True # 声明是主键
|
||||
, autoincrement=True # 声明是自增主键
|
||||
, comment='订单编号,自增主键' # 注释
|
||||
)
|
||||
title = Column( String(50) # 对应数据库的varchar类型
|
||||
,nullable=False # 不允许为null
|
||||
)
|
||||
price = Column(DECIMAL)
|
||||
userid = Column( Integer
|
||||
# ,ForeignKey('user_info_detail.id')
|
||||
,nullable=False ) # 外键约束
|
||||
create_date = Column(DATETIME
|
||||
, default=datetime.now # 默认值是当前时间
|
||||
)
|
||||
update_date = Column(DATETIME
|
||||
, default=datetime.now # 首次创建的时间跟更新时间一致
|
||||
, onupdate=datetime.now
|
||||
)
|
||||
|
||||
class User(Base): # python里的表名字
|
||||
__tablename__ = 'user_info_detail' # 实际数据库的表名字
|
||||
id = Column( Integer # 声明字段的数据类型
|
||||
, primary_key=True # 声明是主键
|
||||
, autoincrement=True # 声明是自增主键
|
||||
, comment='用户编号,自增主键' # 注释
|
||||
)
|
||||
username = Column( String(50) # 对应数据库的varchar类型
|
||||
,nullable=False # 不允许为null
|
||||
,comment='用户名'
|
||||
)
|
||||
create_date = Column(DATETIME
|
||||
, default=datetime.now # 默认值是当前时间
|
||||
)
|
||||
update_date = Column(DATETIME
|
||||
, default=datetime.now # 首次创建的时间跟更新时间一致
|
||||
, onupdate=datetime.now
|
||||
)
|
||||
|
||||
Base.metadata.create_all(engine) # 所有表创建 <- 只要数据库存在这个表了,就不会触发,也不会更新表结构
|
||||
# Order.__table__.create(engine,checkfirst = True) # 单张表的创建
|
||||
|
||||
|
||||
# 三、【表数据的新增】
|
||||
'''1.实例化数据库表模型类,得到具体的数据'''
|
||||
u1 = User( username='清风飞扬' )
|
||||
print( 'User1的属性:',u1.id,u1.username,u1.create_date,u1.update_date )
|
||||
u2 = User( username='北大青鸟' )
|
||||
print( 'User2的属性:',u2.username)
|
||||
|
||||
'''2.将实例化好的数据,添加到数据库里'''
|
||||
# 拿到数据库会话管理工具
|
||||
Session = sessionmaker( bind=engine
|
||||
,autoflush=False # 是否开启自动刷新python新增的数据到实际的数据库内存缓存中
|
||||
,autocommit = False # 不自动提交数据,这个是真正提交到数据库磁盘永久生效的
|
||||
)
|
||||
|
||||
session = Session() # 实例化一个数据库操作会话
|
||||
# session.add( u1 ) # 新增数据到数据库里
|
||||
# session.add( u2 )
|
||||
session.commit() # 提交,永久生效,注意提交后不能回滚
|
||||
|
||||
print(session.query(User).all())
|
||||
#
|
||||
# # session.rollback() # 回滚,事务没有提交前,dml操作可以回滚
|
||||
# session.close() # 关闭数据操作会话
|
||||
#
|
||||
#
|
||||
# '''
|
||||
# 等价于sql语句:
|
||||
# insert into user_info_detail(username) values('清风飞扬');
|
||||
# insert into user_info_detail(username) values('北大青鸟');
|
||||
# '''
|
||||
#
|
||||
# # 四、【表数据的更改】
|
||||
# session = Session() # 实例化一个数据库操作会话
|
||||
# print(f'{session.query(User)=}, {session.query(User).all()=}')
|
||||
# session.query(User)\
|
||||
# .filter( User.username == '北大青鸟' )\
|
||||
# .update( {"username":'沃林数智'
|
||||
# , "create_date":"2000-10-10"
|
||||
# } # 同时更新多个字段
|
||||
# )
|
||||
# session.commit()
|
||||
# '''
|
||||
# 多个复合条件:
|
||||
# filter( 条件1,条件2,条件3.. ) # and的关系
|
||||
#
|
||||
# filter( or_( 条件1,条件2), not_(条件2),and_(条件1,条件2) )
|
||||
#
|
||||
# session.query(表模型).filter(条件1).filter(条件2).filter(条件3) 条件可以拼接成and的关系
|
||||
#
|
||||
# or_: 函数里的条件都是 or 的关系
|
||||
# and_: 函数里的条件是 and 的关系
|
||||
# not_ : 将条件的结果进行取反
|
||||
# '''
|
||||
#
|
||||
# # 五、【表数据的删除】
|
||||
# try:
|
||||
# session.query( User ).filter( User.username == '清风飞扬' ).delete() # 只要命中就删除
|
||||
# session.commit()
|
||||
# except:
|
||||
# session.rollback() # 如果报错,就回滚
|
||||
#
|
||||
# # 清空数据,并且重置自增主键 truncate table 表名; 无法回滚
|
||||
# # session.execute( text( "truncate table user_info_detail" ) ) # 直接书写原生的sql语句
|
||||
# # 可以select、update、delete全部支持
|
||||
|
||||
|
||||
|
||||
'''1.单表基础查询'''
|
||||
# ① 单条数据查询 first(),返回的是一个查询结果对象
|
||||
r1 = session.query(User).first()
|
||||
print('获取单个查询结果对象:',r1)
|
||||
print(r1.id,r1.usernmae,r1.create_date,r1.update_date)
|
||||
|
||||
# ② 全量查询 all() , 返回的是一个列表list
|
||||
r2 = session.query(User).all()
|
||||
print('获取多个查询结果对象:',r2)
|
||||
for i in range( len(r2) ):
|
||||
print(f'第{i+1}行数据:',r2[i].id,r2[i].usernmae,r2[i].create_date,r2[i].update_date)
|
||||
|
||||
q = session.query(User) # 得到一个查询语句对象
|
||||
r3 = q.all() # 获取查询语句对象的所有内容
|
||||
print(q)
|
||||
print(r3)
|
||||
|
||||
# ③ where 条件查询
|
||||
r4 = session.query(User).filter( or_(User.id == 1, User.usernmae == '李四') ).all()
|
||||
for i in r4:
|
||||
print(i.id,i.usernmae)
|
||||
|
||||
# ④ like模糊匹配查询
|
||||
r5 = session.query(User).where( User.usernmae.like('%三%') ).all()
|
||||
for i in r5:
|
||||
print('like模糊匹配:',i.id, i.usernmae)
|
||||
|
||||
# ⑤ order by排序
|
||||
r5 = session.query(User).order_by(User.id.desc(), User.usernmae).all()
|
||||
for i in r5:
|
||||
print('排序:', i.id, i.usernmae)
|
||||
|
||||
# ⑥ limit 分页
|
||||
'''
|
||||
分页公式:
|
||||
原生sql:limit( (页数-1)*每页数据量, 每页数据量 )
|
||||
orm里: offset( (页数-1)*每页数据量 ).limit(每页数据量)
|
||||
'''
|
||||
n = 1 # 页数
|
||||
m = 2 # 每页数据量
|
||||
|
||||
r6 = session.query(User).offset((n-1)*m).limit(m).all()
|
||||
for i in r6:
|
||||
print('分页:', i.id, i.usernmae)
|
||||
|
||||
'''2.分组聚合group by'''
|
||||
'''
|
||||
select usernmae,count(1)
|
||||
from user_info_detail
|
||||
group by usernmae
|
||||
having count(1) >= 2
|
||||
'''
|
||||
r7 = session.query(User.usernmae,func.count(User.id).label('num')).\
|
||||
group_by( User.usernmae ).\
|
||||
having( func.count(User.id) >= 2 ).\
|
||||
all()
|
||||
|
||||
for i in r7:
|
||||
print('分组聚合:',i.usernmae,i.num)
|
||||
|
||||
'''
|
||||
func函数支持:max、min、sum、count、avg常用的聚合函数
|
||||
用法: func.count(聚合的字段)
|
||||
'''
|
||||
|
||||
'''3.多表关联查询join内连接/outerjoin左连接'''
|
||||
r8 = session.query(User.id.label('user_id') , User.usernmae, Order.id.label('order_id'), Order.title ).\
|
||||
join( Order, User.id == Order.userid ).\
|
||||
order_by(User.id).\
|
||||
all()
|
||||
|
||||
for i in r8:
|
||||
print('多表关联内连接:',i.user_id,i.usernmae,i.order_id,i.title)
|
||||
|
||||
# outerjoin(谁放在里面,谁就是从表)
|
||||
r8 = session.query(User.id.label('user_id') , User.usernmae, Order.id.label('order_id'), Order.title ).\
|
||||
outerjoin( Order, User.id == Order.userid ).\
|
||||
order_by(User.id).\
|
||||
all()
|
||||
|
||||
for i in r8:
|
||||
print('多表关联左连接:',i.user_id,i.usernmae,i.order_id,i.title)
|
||||
|
||||
'''
|
||||
select ——> session.query( 表模型名 )
|
||||
from 表1
|
||||
join 表2 on 条件 ——> join(连接的表,条件) / outerjoin(连接的表,条件)
|
||||
where 筛选条件 ——> filter(条件)
|
||||
group by 分组字段 ——> group_by(分组的字段)
|
||||
having 分组后过滤 ——> having(过滤的条件)
|
||||
order by 排序 ——> order_by( 排序的字段.desc() )
|
||||
limit 分页 ——> offset(偏移量).limit(数据量)
|
||||
'''
|
||||
|
||||
n = 1 # 页数
|
||||
m = 2 # 每页数据量
|
||||
r9 = session.query(User.usernmae,Order.title,func.count(1).label('ordernum')).\
|
||||
join(Order,User.id == Order.userid).\
|
||||
filter(Order.title == '苹果').\
|
||||
group_by(User.usernmae,Order.title).\
|
||||
having(func.count(1) >= 1).\
|
||||
order_by(func.count(1).desc()).\
|
||||
offset((n-1)*m).limit(m).\
|
||||
all()
|
||||
|
||||
for i in r9:
|
||||
print('完整的查询:',i.usernmae,i.title,i.ordernum)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
from datetime import datetime
|
||||
from typing import List, Optional
|
||||
|
||||
from sqlalchemy import (
|
||||
create_engine, String, Integer, ForeignKey, DateTime, select, func
|
||||
)
|
||||
from sqlalchemy.orm import (
|
||||
DeclarativeBase, Mapped, mapped_column, relationship,
|
||||
Session, selectinload
|
||||
)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 1. 定义 Base 和模型
|
||||
# ============================================================
|
||||
|
||||
class Base(DeclarativeBase):
|
||||
"""所有模型的基类,SQLAlchemy 2.0 推荐写法"""
|
||||
pass
|
||||
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = "users"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||
name: Mapped[str] = mapped_column(String(50), nullable=False)
|
||||
email: Mapped[str] = mapped_column(String(100), unique=True, nullable=False)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.now)
|
||||
|
||||
# 一对多:一个用户有多个文章
|
||||
posts: Mapped[List["Post"]] = relationship(
|
||||
back_populates="author",
|
||||
cascade="all, delete-orphan", # 删除用户时级联删除文章
|
||||
lazy="selectin" # 查询用户时自动预加载文章
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<User(id={self.id}, name='{self.name}', email='{self.email}')>"
|
||||
|
||||
|
||||
class Post(Base):
|
||||
__tablename__ = "posts"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||
title: Mapped[str] = mapped_column(String(200), nullable=False)
|
||||
content: Mapped[Optional[str]] = mapped_column(String(1000))
|
||||
user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), nullable=False)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.now)
|
||||
|
||||
# 多对一:多篇文章属于一个用户
|
||||
author: Mapped["User"] = relationship(back_populates="posts")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Post(id={self.id}, title='{self.title}', user_id={self.user_id})>"
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 2. 创建引擎和表
|
||||
# ============================================================
|
||||
|
||||
engine = create_engine("sqlite:///users_posts.db", echo=False)
|
||||
Base.metadata.create_all(engine)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 3. CRUD 操作
|
||||
# ============================================================
|
||||
|
||||
def create_user(session: Session, name: str, email: str) -> User:
|
||||
user = User(name=name, email=email)
|
||||
session.add(user)
|
||||
session.commit()
|
||||
session.refresh(user) # 刷新以获取数据库生成的 id
|
||||
return user
|
||||
|
||||
|
||||
def create_post(session: Session, title: str, content: str, user_id: int) -> Post:
|
||||
post = Post(title=title, content=content, user_id=user_id)
|
||||
session.add(post)
|
||||
session.commit()
|
||||
session.refresh(post)
|
||||
return post
|
||||
|
||||
|
||||
def get_user_by_id(session: Session, user_id: int) -> Optional[User]:
|
||||
return session.get(User, user_id)
|
||||
|
||||
|
||||
def get_user_by_email(session: Session, email: str) -> Optional[User]:
|
||||
stmt = select(User).where(User.email == email)
|
||||
return session.execute(stmt).scalar_one_or_none()
|
||||
|
||||
|
||||
def get_all_users(session: Session) -> List[User]:
|
||||
stmt = select(User).order_by(User.id)
|
||||
return list(session.execute(stmt).scalars().all())
|
||||
|
||||
|
||||
def update_user_email(session: Session, user_id: int, new_email: str) -> bool:
|
||||
user = session.get(User, user_id)
|
||||
if not user:
|
||||
return False
|
||||
user.email = new_email
|
||||
session.commit()
|
||||
return True
|
||||
|
||||
|
||||
def delete_user(session: Session, user_id: int) -> bool:
|
||||
user = session.get(User, user_id)
|
||||
if not user:
|
||||
return False
|
||||
session.delete(user) # cascade 会自动删除关联的 posts
|
||||
session.commit()
|
||||
return True
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 4. 复杂查询
|
||||
# ============================================================
|
||||
|
||||
def get_users_with_post_count(session: Session):
|
||||
"""统计每个用户的文章数"""
|
||||
stmt = (
|
||||
select(User.name, func.count(Post.id).label("post_count"))
|
||||
.outerjoin(Post, User.id == Post.user_id)
|
||||
.group_by(User.id)
|
||||
.order_by(func.count(Post.id).desc())
|
||||
)
|
||||
return session.execute(stmt).all()
|
||||
|
||||
|
||||
def get_posts_by_user(session: Session, user_id: int) -> List[Post]:
|
||||
"""查询某个用户的所有文章"""
|
||||
stmt = select(Post).where(Post.user_id == user_id).order_by(Post.created_at.desc())
|
||||
return list(session.execute(stmt).scalars().all())
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 5. 演示
|
||||
# ============================================================
|
||||
|
||||
def main():
|
||||
with Session(engine) as session:
|
||||
# 清理旧数据(演示用)
|
||||
session.query(Post).delete()
|
||||
session.query(User).delete()
|
||||
session.commit()
|
||||
|
||||
# 创建用户
|
||||
alice = create_user(session, "Alice", "alice@example.com")
|
||||
bob = create_user(session, "Bob", "bob@example.com")
|
||||
print("创建用户:", alice, bob)
|
||||
|
||||
# 创建文章
|
||||
create_post(session, "Alice 的第一篇", "Hello world", alice.id)
|
||||
create_post(session, "Alice 的第二篇", "SQLAlchemy 真香", alice.id)
|
||||
create_post(session, "Bob 的独苗", "Python ORM", bob.id)
|
||||
|
||||
# 查询
|
||||
print("\n所有用户:")
|
||||
for u in get_all_users(session):
|
||||
print(f" {u} -> 文章: {[p.title for p in u.posts]}")
|
||||
|
||||
print("\n用户文章数统计:")
|
||||
for name, count in get_users_with_post_count(session):
|
||||
print(f" {name}: {count} 篇")
|
||||
|
||||
# 更新
|
||||
update_user_email(session, alice.id, "alice_new@example.com")
|
||||
print("\n更新后:", get_user_by_id(session, alice.id))
|
||||
|
||||
# 删除(级联删除文章)
|
||||
delete_user(session, bob.id)
|
||||
print("\n删除 Bob 后,所有用户:")
|
||||
for u in get_all_users(session):
|
||||
print(f" {u}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Binary file not shown.
@@ -0,0 +1,97 @@
|
||||
class Field:
|
||||
"""字段描述符:记录列名和类型"""
|
||||
def __init__(self, column_type):
|
||||
self.column_type = column_type
|
||||
self.name = None
|
||||
|
||||
def __set_name__(self, owner, name):
|
||||
self.name = name
|
||||
|
||||
def __get__(self, instance, owner):
|
||||
if instance is None:
|
||||
return self
|
||||
return instance.__dict__.get(self.name)
|
||||
|
||||
def __set__(self, instance, value):
|
||||
instance.__dict__[self.name] = value
|
||||
|
||||
|
||||
class IntegerField(Field):
|
||||
def __init__(self, primary_key=False):
|
||||
super().__init__("INTEGER")
|
||||
self.primary_key = primary_key
|
||||
|
||||
|
||||
class StringField(Field):
|
||||
def __init__(self, max_length=255):
|
||||
super().__init__(f"VARCHAR({max_length})")
|
||||
|
||||
|
||||
class ModelMeta(type):
|
||||
"""元类:在类创建时收集字段,生成 SQL"""
|
||||
def __new__(mcs, name, bases, namespace):
|
||||
# 跳过基类 Model 本身
|
||||
if name == "Model":
|
||||
return super().__new__(mcs, name, bases, namespace)
|
||||
|
||||
fields = {}
|
||||
for key, value in namespace.items():
|
||||
if isinstance(value, Field):
|
||||
fields[key] = value
|
||||
|
||||
# 把收集到的字段挂到类上
|
||||
namespace["_fields"] = fields
|
||||
namespace["_table"] = name.lower() + "s"
|
||||
|
||||
cls = super().__new__(mcs, name, bases, namespace)
|
||||
return cls
|
||||
|
||||
|
||||
class Model(metaclass=ModelMeta):
|
||||
"""所有模型的基类"""
|
||||
|
||||
@classmethod
|
||||
def create_table_sql(cls):
|
||||
columns = []
|
||||
for name, field in cls._fields.items():
|
||||
col = f"{name} {field.column_type}"
|
||||
if isinstance(field, IntegerField) and field.primary_key:
|
||||
col += " PRIMARY KEY AUTOINCREMENT"
|
||||
columns.append(col)
|
||||
return f"CREATE TABLE IF NOT EXISTS {cls._table} ({', '.join(columns)});"
|
||||
|
||||
@classmethod
|
||||
def insert_sql(cls, **kwargs):
|
||||
keys = ", ".join(kwargs.keys())
|
||||
values = ", ".join(
|
||||
f"'{v}'" if isinstance(v, str) else str(v)
|
||||
for v in kwargs.values()
|
||||
)
|
||||
return f"INSERT INTO {cls._table} ({keys}) VALUES ({values});"
|
||||
|
||||
|
||||
# ===== 使用 =====
|
||||
class User(Model):
|
||||
id = IntegerField(primary_key=True)
|
||||
name = StringField(50)
|
||||
email = StringField(100)
|
||||
|
||||
|
||||
class Post(Model):
|
||||
id = IntegerField(primary_key=True)
|
||||
title = StringField(200)
|
||||
user_id = IntegerField()
|
||||
|
||||
|
||||
print(User.create_table_sql())
|
||||
# CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(50), email VARCHAR(100));
|
||||
|
||||
print(User.insert_sql(name="Alice", email="alice@example.com"))
|
||||
# INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com');
|
||||
|
||||
print(Post.create_table_sql())
|
||||
# CREATE TABLE IF NOT EXISTS posts (id INTEGER PRIMARY KEY AUTOINCREMENT, title VARCHAR(200), user_id INTEGER);
|
||||
|
||||
print(Post.insert_sql(title="orm is best", user_id=1))
|
||||
print(Post.insert_sql(title="orm simple imp", user_id=1))
|
||||
print(Post.insert_sql(title="mete class is the base", user_id=1))
|
||||
Binary file not shown.
@@ -0,0 +1,24 @@
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class User(BaseModel):
|
||||
id: int
|
||||
name: str = "John Doe"
|
||||
signup_ts: datetime | None = None
|
||||
friends: list[int] = []
|
||||
|
||||
|
||||
external_data = {
|
||||
"id": "123",
|
||||
"signup_ts": "2017-06-01 12:22",
|
||||
"friends": [1, "2", b"3"],
|
||||
}
|
||||
user = User(**external_data)
|
||||
print(user)
|
||||
# > User id=123 name='John Doe' signup_ts=datetime.datetime(2017, 6, 1, 12, 22) friends=[1, 2, 3]
|
||||
print(user.id)
|
||||
# > 123
|
||||
|
||||
print(type(user.friends[0]),type(user.friends[1]),type(user.friends[2]))
|
||||
@@ -0,0 +1,55 @@
|
||||
class RouterMeta(type):
|
||||
"""元类:自动收集被 @route 标记的方法"""
|
||||
def __new__(mcs, name, bases, namespace):
|
||||
routes = {}
|
||||
for key, value in namespace.items():
|
||||
if hasattr(value, "_route_path"):
|
||||
routes[value._route_path] = (value._http_method, key)
|
||||
namespace["_routes"] = routes
|
||||
return super().__new__(mcs, name, bases, namespace)
|
||||
|
||||
|
||||
def route(path, method="GET"):
|
||||
"""装饰器:给方法打上路由标记"""
|
||||
def decorator(func):
|
||||
func._route_path = path
|
||||
func._http_method = method
|
||||
return func
|
||||
return decorator
|
||||
|
||||
|
||||
class Controller(metaclass=RouterMeta):
|
||||
"""所有控制器的基类"""
|
||||
|
||||
@classmethod
|
||||
def dispatch(cls, path, method):
|
||||
if path not in cls._routes:
|
||||
return 404, "Not Found"
|
||||
http_method, func_name = cls._routes[path]
|
||||
if http_method != method:
|
||||
return 405, "Method Not Allowed"
|
||||
return 200, getattr(cls(), func_name)()
|
||||
|
||||
|
||||
# ===== 使用 =====
|
||||
class UserController(Controller):
|
||||
@route("/users", "GET")
|
||||
def list_users(self):
|
||||
return [{"id": 1, "name": "Alice"}]
|
||||
|
||||
@route("/users/create", "POST")
|
||||
def create_user(self):
|
||||
return {"status": "created"}
|
||||
|
||||
|
||||
print(UserController._routes)
|
||||
# {'/users': ('GET', 'list_users'), '/users/create': ('POST', 'create_user')}
|
||||
|
||||
print(UserController.dispatch("/users", "GET"))
|
||||
# (200, [{'id': 1, 'name': 'Alice'}])
|
||||
|
||||
print(UserController.dispatch("/users", "POST"))
|
||||
# (405, 'Method Not Allowed')
|
||||
|
||||
print(UserController.dispatch("/unknown", "GET"))
|
||||
# (404, 'Not Found')
|
||||
@@ -0,0 +1,24 @@
|
||||
class Class:
|
||||
name = 'Class'
|
||||
|
||||
def __init__(self, at):
|
||||
self.attribute = at
|
||||
|
||||
def change(self, ano):
|
||||
self.attribute = ano
|
||||
self.name = ano
|
||||
self.__class__.name = ano
|
||||
|
||||
|
||||
def change_obj(self, ano):
|
||||
self.attribute = ano
|
||||
self.name = ano
|
||||
|
||||
|
||||
clazz = Class('abc')
|
||||
print(clazz.name)
|
||||
|
||||
# clazz.change('good')
|
||||
# print(Class.name, clazz.name, clazz.attribute)
|
||||
clazz.change_obj('good')
|
||||
print(Class.name, clazz.name, clazz.attribute)
|
||||
@@ -0,0 +1,46 @@
|
||||
# log.py
|
||||
import logging
|
||||
|
||||
class Log:
|
||||
# name, level no need to record
|
||||
def __init__(self, name):
|
||||
self.__log_name = name
|
||||
self.__log_levlel = None
|
||||
# deal duplicate handler
|
||||
self.__handlers = []
|
||||
# init default logger
|
||||
self.set_console()
|
||||
|
||||
def set_console(self):
|
||||
self.logger = logging.getLogger(self.__log_name)
|
||||
# self.logger.setLevel(eval("logging." + level.upper()))
|
||||
fh = logging.StreamHandler()
|
||||
log_format = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
,datefmt='%Y-%m-%d %H:%M:%S')
|
||||
fh.setFormatter(log_format)
|
||||
if fh not in self.__handlers:
|
||||
self.logger.addHandler(fh)
|
||||
self.__handlers.append(fh)
|
||||
|
||||
def set_file(self, file_name):
|
||||
self.logger = logging.getLogger(self.__log_name)
|
||||
# self.logger.setLevel(eval("logging." + level.upper()))
|
||||
fh = logging.FileHandler(file_name,encoding='utf-8') # FileHandler 的默认模式是 'a',用 'a+' 在某些平台上可能导致文件指针行为异常。
|
||||
log_format = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
,datefmt='%Y-%m-%d %H:%M:%S')
|
||||
fh.setFormatter(log_format)
|
||||
if fh not in self.__handlers:
|
||||
self.logger.addHandler(fh)
|
||||
self.__handlers.append(fh)
|
||||
|
||||
def message(self,level,message):
|
||||
# self.logger.log(eval("logging." + level.upper()),message) # not safe for eval
|
||||
self.logger.log(getattr(logging,level.upper()),message)
|
||||
|
||||
|
||||
|
||||
l = Log('ai-model')
|
||||
l.message("warning","test Log class to console")
|
||||
|
||||
# l.set_file('libplus.log')
|
||||
# l.message("info","test info Log to file")
|
||||
@@ -0,0 +1,4 @@
|
||||
2026-09-17 08:43:50 - ai-model - INFO - ai-model - 0
|
||||
2026-09-17 08:43:50 - ai-model - INFO - ai-model - 1
|
||||
2026-09-17 08:51:39 - ai-model - INFO - ai-model - 0
|
||||
2026-09-17 08:51:39 - ai-model - INFO - ai-model - 1
|
||||
@@ -0,0 +1,44 @@
|
||||
import logging,os
|
||||
|
||||
class Log:
|
||||
def __init__(self, name, level="INFO"):
|
||||
self.logger = logging.getLogger(name)
|
||||
self.logger.setLevel(getattr(logging, level.upper()))
|
||||
# fix duplicated name logger
|
||||
if not self.logger.hasHandlers():
|
||||
self.set_console()
|
||||
|
||||
def set_console(self):
|
||||
fh = logging.StreamHandler()
|
||||
fh.setFormatter(logging.Formatter(
|
||||
'%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
))
|
||||
if not self.logger.hasHandlers():
|
||||
self.logger.addHandler(fh)
|
||||
|
||||
def set_file(self, file_name):
|
||||
# 检查是否已有 FileHandler 指向同一个文件
|
||||
for h in self.logger.handlers:
|
||||
if isinstance(h, logging.FileHandler) and h.baseFilename == os.path.abspath(file_name):
|
||||
return # 已存在,直接返回,不重复添加
|
||||
|
||||
fh = logging.FileHandler(file_name, encoding='utf-8')
|
||||
fh.setFormatter(logging.Formatter(
|
||||
'%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
))
|
||||
print(f'{self.logger.hasHandlers()=}')
|
||||
self.logger.addHandler(fh)
|
||||
|
||||
def message(self, level, message):
|
||||
self.logger.log(getattr(logging, level.upper()), message)
|
||||
|
||||
# single instance
|
||||
l = Log('ai-model')
|
||||
l1 = Log('ai-model')
|
||||
|
||||
l.set_file("logplus.log")
|
||||
l1.set_file("logplus.log")
|
||||
l.message("info", "ai-model - 0")
|
||||
l1.message("info", "ai-model - 1")
|
||||
@@ -0,0 +1,53 @@
|
||||
import logging
|
||||
import os
|
||||
|
||||
class Log:
|
||||
_added = {} # 类级别:记录每个 logger 已添加的 Handler 标识
|
||||
|
||||
def __init__(self, name, level="INFO"):
|
||||
self.logger = logging.getLogger(name)
|
||||
self.logger.setLevel(getattr(logging, level.upper()))
|
||||
self.logger.propagate = False # 避免向 root 传播导致重复
|
||||
if not self.logger.hasHandlers():
|
||||
self.set_console()
|
||||
|
||||
def _handler_exists(self, key):
|
||||
return key in Log._added.get(self.logger.name, set())
|
||||
|
||||
def _mark_handler(self, key):
|
||||
Log._added.setdefault(self.logger.name, set()).add(key)
|
||||
|
||||
def set_console(self):
|
||||
if self._handler_exists("console"):
|
||||
return
|
||||
fh = logging.StreamHandler()
|
||||
fh.setFormatter(logging.Formatter(
|
||||
'%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
))
|
||||
self.logger.addHandler(fh)
|
||||
self._mark_handler("console")
|
||||
|
||||
def set_file(self, file_name):
|
||||
key = f"file:{os.path.abspath(file_name)}"
|
||||
if self._handler_exists(key):
|
||||
return
|
||||
fh = logging.FileHandler(file_name, encoding='utf-8')
|
||||
fh.setFormatter(logging.Formatter(
|
||||
'%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
))
|
||||
self.logger.addHandler(fh)
|
||||
self._mark_handler(key)
|
||||
|
||||
def message(self, level, message):
|
||||
self.logger.log(getattr(logging, level.upper()), message)
|
||||
|
||||
|
||||
l = Log('ai-model')
|
||||
l1 = Log('ai-model')
|
||||
|
||||
l.set_file("logplus.log")
|
||||
l1.set_file("logplus.log")
|
||||
l.message("info", "ai-model - 0")
|
||||
l1.message("info", "ai-model - 1")
|
||||
+388
@@ -0,0 +1,388 @@
|
||||
2026-09-18 16:24:53 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 16:24:53 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 16:24:53 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 16:24:53 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 16:47:18 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 16:47:18 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 16:47:19 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 16:47:19 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 16:49:01 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 16:49:01 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 16:49:03 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 16:49:03 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 16:50:05 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 16:50:05 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 16:50:06 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 16:50:06 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 16:50:08 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 16:50:08 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 16:53:40 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 16:53:40 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 16:53:41 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 16:53:41 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 16:53:57 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 16:53:57 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 16:53:58 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 16:53:58 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 16:57:05 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 16:57:05 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 16:57:06 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 16:57:06 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 16:58:19 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 16:58:19 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 16:58:20 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 16:58:20 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 17:00:58 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 17:00:58 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 17:00:59 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 17:00:59 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 17:01:36 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 17:01:36 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 17:01:37 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 17:01:37 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 17:05:30 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 17:05:30 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 17:05:31 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 17:05:31 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 17:08:13 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 17:08:13 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 17:08:46 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 17:08:46 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 17:08:47 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 17:08:47 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 17:11:19 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 17:11:19 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 17:38:34 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 17:38:34 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 17:38:46 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 17:38:46 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 17:39:43 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 17:39:43 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 17:39:44 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 17:39:44 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 17:39:50 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 17:39:50 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 17:39:51 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 17:39:51 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 17:39:59 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 17:39:59 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 17:40:00 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 17:40:00 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 17:52:13 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 17:52:13 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 18:23:08 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 18:23:08 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 18:23:19 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 18:23:19 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 18:23:40 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 18:23:40 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 18:24:49 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 18:24:49 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 18:37:13 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 18:37:13 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 18:38:18 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 18:38:18 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 18:38:19 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 18:38:19 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 18:38:37 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 18:38:37 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 18:38:38 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 18:38:38 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 18:38:40 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 18:38:40 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 18:38:41 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 18:38:41 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 18:38:54 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 18:38:54 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 18:38:55 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 18:38:55 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 18:39:06 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 18:39:06 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 18:39:35 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 18:39:35 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 18:39:39 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 18:39:39 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 18:39:40 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 18:39:40 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 18:40:21 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 18:40:21 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 18:40:21 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 18:40:21 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 18:40:28 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 18:40:28 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 18:40:29 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 18:40:29 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 18:41:07 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 18:41:07 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 18:41:07 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 18:41:07 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 18:41:41 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 18:41:41 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 18:42:10 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 18:42:10 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 19:37:06 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 19:37:06 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 19:37:45 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 19:37:45 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 19:37:46 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 19:37:46 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 19:38:18 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 19:38:18 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 19:38:54 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 19:38:54 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 19:38:55 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 19:38:55 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 19:39:12 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 19:39:12 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 19:39:13 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 19:39:13 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 19:39:51 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 19:39:51 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 19:39:52 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 19:39:52 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 20:06:50 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 20:06:50 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 20:06:51 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 20:06:51 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 21:35:37 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 21:35:37 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 21:35:40 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 21:35:40 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 22:18:23 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 22:18:23 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 22:20:42 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 22:20:42 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 22:20:43 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 22:20:43 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 22:42:41 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 22:42:41 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 22:42:58 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 22:42:58 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 22:43:03 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 22:43:03 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:17:24 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:17:24 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:17:41 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:17:41 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:17:56 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:17:56 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:17:57 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:17:57 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:18:43 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:18:43 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:19:18 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:19:18 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:21:05 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:21:05 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:21:25 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:21:25 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:24:03 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:24:03 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:24:04 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:24:04 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:27:30 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:27:30 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:27:31 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:27:31 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:28:16 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:28:16 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:28:17 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:28:17 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:30:44 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:30:44 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:30:46 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:30:46 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:31:47 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:31:47 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:32:17 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:32:17 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:32:18 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:32:18 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:32:52 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:32:52 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:32:53 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:32:53 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:33:22 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:33:22 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:33:23 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:33:23 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:33:36 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:33:36 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:33:37 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:33:37 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:33:42 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:33:42 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:33:43 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:33:43 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:33:47 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:33:47 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:33:48 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:33:48 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:34:02 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:34:02 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:34:03 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:34:03 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:34:14 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:34:14 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:34:15 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:34:15 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:34:29 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:34:29 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:34:30 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:34:30 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:35:11 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:35:11 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:35:18 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:35:18 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:37:55 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:37:55 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:37:56 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:37:56 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:43:44 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:43:44 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:43:45 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:43:45 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:43:51 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:43:51 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:43:52 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:43:52 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:44:26 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:44:26 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:44:27 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:44:27 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:46:53 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:46:53 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:46:54 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:46:54 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:51:35 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:51:35 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:51:36 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:51:36 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:54:31 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:54:31 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:54:32 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:54:32 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:54:39 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:54:39 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:54:41 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:54:41 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:54:43 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:54:43 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:54:44 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:54:44 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:54:47 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:54:47 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:54:48 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:54:48 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:55:34 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:55:34 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:55:35 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:55:35 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:57:07 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:57:07 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:57:08 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:57:08 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:57:27 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:57:27 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:57:28 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:57:28 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:57:50 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:57:50 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:57:51 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:57:51 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:58:23 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:58:23 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:58:24 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:58:24 - ai-model - INFO - ai-model - 1
|
||||
2026-09-18 23:59:39 - ai-model - INFO - ai-model - 0
|
||||
2026-09-18 23:59:39 - ai-model - INFO - ai-model - 1
|
||||
2026-09-19 00:03:24 - ai-model - INFO - ai-model - 0
|
||||
2026-09-19 00:03:24 - ai-model - INFO - ai-model - 1
|
||||
2026-09-19 00:03:25 - ai-model - INFO - ai-model - 0
|
||||
2026-09-19 00:03:25 - ai-model - INFO - ai-model - 1
|
||||
2026-09-19 00:04:13 - ai-model - INFO - ai-model - 0
|
||||
2026-09-19 00:04:13 - ai-model - INFO - ai-model - 1
|
||||
2026-09-19 00:04:14 - ai-model - INFO - ai-model - 0
|
||||
2026-09-19 00:04:14 - ai-model - INFO - ai-model - 1
|
||||
2026-09-19 09:52:47 - ai-model - INFO - ai-model - 0
|
||||
2026-09-19 09:52:47 - ai-model - INFO - ai-model - 1
|
||||
2026-09-19 09:56:37 - ai-model - INFO - ai-model - 0
|
||||
2026-09-19 09:56:37 - ai-model - INFO - ai-model - 1
|
||||
2026-09-19 09:56:38 - ai-model - INFO - ai-model - 0
|
||||
2026-09-19 09:56:38 - ai-model - INFO - ai-model - 1
|
||||
2026-09-19 09:56:55 - ai-model - INFO - ai-model - 0
|
||||
2026-09-19 09:56:55 - ai-model - INFO - ai-model - 1
|
||||
2026-09-19 09:58:10 - ai-model - INFO - ai-model - 0
|
||||
2026-09-19 09:58:10 - ai-model - INFO - ai-model - 1
|
||||
2026-09-19 09:58:11 - ai-model - INFO - ai-model - 0
|
||||
2026-09-19 09:58:11 - ai-model - INFO - ai-model - 1
|
||||
2026-09-19 09:58:47 - ai-model - INFO - ai-model - 0
|
||||
2026-09-19 09:58:47 - ai-model - INFO - ai-model - 1
|
||||
2026-09-19 09:59:15 - ai-model - INFO - ai-model - 0
|
||||
2026-09-19 09:59:15 - ai-model - INFO - ai-model - 1
|
||||
2026-09-19 09:59:16 - ai-model - INFO - ai-model - 0
|
||||
2026-09-19 09:59:16 - ai-model - INFO - ai-model - 1
|
||||
2026-09-19 10:01:22 - ai-model - INFO - ai-model - 0
|
||||
2026-09-19 10:01:22 - ai-model - INFO - ai-model - 1
|
||||
2026-09-19 10:01:34 - ai-model - INFO - ai-model - 0
|
||||
2026-09-19 10:01:34 - ai-model - INFO - ai-model - 1
|
||||
2026-09-19 10:01:56 - ai-model - INFO - ai-model - 0
|
||||
2026-09-19 10:01:56 - ai-model - INFO - ai-model - 1
|
||||
2026-09-19 10:01:57 - ai-model - INFO - ai-model - 0
|
||||
2026-09-19 10:01:57 - ai-model - INFO - ai-model - 1
|
||||
2026-09-19 10:05:20 - ai-model - INFO - ai-model - 0
|
||||
2026-09-19 10:05:20 - ai-model - INFO - ai-model - 1
|
||||
2026-09-19 10:05:21 - ai-model - INFO - ai-model - 0
|
||||
2026-09-19 10:05:21 - ai-model - INFO - ai-model - 1
|
||||
2026-09-19 10:06:46 - ai-model - INFO - ai-model - 0
|
||||
2026-09-19 10:06:46 - ai-model - INFO - ai-model - 1
|
||||
2026-09-19 10:08:09 - ai-model - INFO - ai-model - 0
|
||||
2026-09-19 10:08:09 - ai-model - INFO - ai-model - 1
|
||||
2026-09-19 10:08:10 - ai-model - INFO - ai-model - 0
|
||||
2026-09-19 10:08:10 - ai-model - INFO - ai-model - 1
|
||||
2026-09-19 10:08:45 - ai-model - INFO - ai-model - 0
|
||||
2026-09-19 10:08:45 - ai-model - INFO - ai-model - 1
|
||||
2026-09-19 10:08:46 - ai-model - INFO - ai-model - 0
|
||||
2026-09-19 10:08:46 - ai-model - INFO - ai-model - 1
|
||||
2026-09-19 10:09:05 - ai-model - INFO - ai-model - 0
|
||||
2026-09-19 10:09:05 - ai-model - INFO - ai-model - 1
|
||||
2026-09-19 10:09:08 - ai-model - INFO - ai-model - 0
|
||||
2026-09-19 10:09:08 - ai-model - INFO - ai-model - 1
|
||||
2026-09-19 10:09:19 - ai-model - INFO - ai-model - 0
|
||||
2026-09-19 10:09:19 - ai-model - INFO - ai-model - 1
|
||||
2026-09-19 10:09:21 - ai-model - INFO - ai-model - 0
|
||||
2026-09-19 10:09:21 - ai-model - INFO - ai-model - 1
|
||||
2026-09-19 10:09:35 - ai-model - INFO - ai-model - 0
|
||||
2026-09-19 10:09:35 - ai-model - INFO - ai-model - 1
|
||||
2026-09-19 10:09:36 - ai-model - INFO - ai-model - 0
|
||||
2026-09-19 10:09:36 - ai-model - INFO - ai-model - 1
|
||||
2026-09-19 10:10:04 - ai-model - INFO - ai-model - 0
|
||||
2026-09-19 10:10:04 - ai-model - INFO - ai-model - 1
|
||||
2026-09-19 10:10:08 - ai-model - INFO - ai-model - 0
|
||||
2026-09-19 10:10:08 - ai-model - INFO - ai-model - 1
|
||||
2026-09-19 10:10:09 - ai-model - INFO - ai-model - 0
|
||||
2026-09-19 10:10:09 - ai-model - INFO - ai-model - 1
|
||||
2026-09-19 10:10:31 - ai-model - INFO - ai-model - 0
|
||||
2026-09-19 10:10:31 - ai-model - INFO - ai-model - 1
|
||||
2026-09-19 10:10:57 - ai-model - INFO - ai-model - 0
|
||||
2026-09-19 10:10:57 - ai-model - INFO - ai-model - 1
|
||||
2026-09-19 10:10:58 - ai-model - INFO - ai-model - 0
|
||||
2026-09-19 10:10:58 - ai-model - INFO - ai-model - 1
|
||||
2026-09-19 10:11:32 - ai-model - INFO - ai-model - 0
|
||||
2026-09-19 10:11:32 - ai-model - INFO - ai-model - 1
|
||||
2026-09-19 10:11:33 - ai-model - INFO - ai-model - 0
|
||||
2026-09-19 10:11:33 - ai-model - INFO - ai-model - 1
|
||||
2026-09-19 10:11:35 - ai-model - INFO - ai-model - 0
|
||||
2026-09-19 10:11:35 - ai-model - INFO - ai-model - 1
|
||||
2026-09-19 10:11:36 - ai-model - INFO - ai-model - 0
|
||||
2026-09-19 10:11:36 - ai-model - INFO - ai-model - 1
|
||||
2026-09-19 10:11:45 - ai-model - INFO - ai-model - 0
|
||||
2026-09-19 10:11:45 - ai-model - INFO - ai-model - 1
|
||||
2026-09-19 10:11:46 - ai-model - INFO - ai-model - 0
|
||||
2026-09-19 10:11:46 - ai-model - INFO - ai-model - 1
|
||||
2026-09-19 10:12:16 - ai-model - INFO - ai-model - 0
|
||||
2026-09-19 10:12:16 - ai-model - INFO - ai-model - 1
|
||||
2026-09-19 10:13:19 - ai-model - INFO - ai-model - 0
|
||||
2026-09-19 10:13:19 - ai-model - INFO - ai-model - 1
|
||||
2026-09-19 10:13:20 - ai-model - INFO - ai-model - 0
|
||||
2026-09-19 10:13:20 - ai-model - INFO - ai-model - 1
|
||||
@@ -0,0 +1,85 @@
|
||||
from fastapi import FastAPI,Request
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
|
||||
from starlette.staticfiles import StaticFiles
|
||||
from router.features import form, method, file, validation, mimetype, path_query_variables, json
|
||||
|
||||
from router.dbopt import mysql,order
|
||||
|
||||
from router.workhome import wk_0915,wk_0916,wk_0917
|
||||
|
||||
app = FastAPI(
|
||||
swagger_ui_parameters={
|
||||
"docExpansion": "none", # 完全折叠,所有 Tags 默认收起
|
||||
"defaultModelsExpandDepth": -1 # 顺便把底部的 Schemas 模型也收起来
|
||||
},
|
||||
title="fastapi tutor - gk"
|
||||
)
|
||||
|
||||
# request response model
|
||||
app.include_router(mimetype.route)
|
||||
app.include_router(method.route)
|
||||
app.include_router(path_query_variables.route)
|
||||
app.include_router(validation.route)
|
||||
app.include_router(form.route)
|
||||
app.include_router(file.route)
|
||||
app.include_router(json.route)
|
||||
|
||||
|
||||
app.include_router(mysql.route)
|
||||
app.include_router(order.route)
|
||||
|
||||
# homeworks...
|
||||
app.include_router(wk_0915.route)
|
||||
app.include_router(wk_0916.route)
|
||||
app.include_router(wk_0917.route)
|
||||
# for r in app.routes:
|
||||
# print(r.path, r.methods, r.name)
|
||||
|
||||
app.mount(path="/static",app=StaticFiles(directory="/home/geeker/Pictures"),name="sn")
|
||||
|
||||
@app.get("/proxy")
|
||||
def get(request: Request):
|
||||
path = request.url_for('sn',path = 'head.png')
|
||||
return path
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"], # 允许所有来源,开发阶段用
|
||||
# allow_origins=["null","http://localhost:8000"], # 允许所有来源,开发阶段用
|
||||
allow_credentials=True, # 允许携带 Cookie
|
||||
allow_methods=["*"], # 允许所有 HTTP 方法
|
||||
allow_headers=["*"], # 允许所有请求头
|
||||
)
|
||||
|
||||
from middle import middles
|
||||
app.middleware("http")(middles.black)
|
||||
app.middleware("http")(middles.time_)
|
||||
# black_list = ['192.168.1.40','192.168.1.94'] # '127.0.0.1','192.168.1.26‘
|
||||
# @app.middleware("http",)
|
||||
# async def middleware(req:Request, next):
|
||||
# if req.client.host in black_list:
|
||||
# return Response("you are limited to access!")
|
||||
# res = await next(req)
|
||||
# # print('exite http',req.client.host)
|
||||
# return res
|
||||
#
|
||||
# @app.middleware("https")
|
||||
# async def middleware(req:Request, next):
|
||||
# print('enter https',time.time())
|
||||
# res = await next(req)
|
||||
# print('exite https',time.time())
|
||||
# return res
|
||||
#
|
||||
# @app.middleware("websocket")
|
||||
# async def middleware(req:Request, next):
|
||||
# print('enter websocket',req.client.host)
|
||||
# res = await next(req)
|
||||
# print('exite websocket',req.client.host)
|
||||
# return res
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
uvicorn.run("main:app",port=9090)
|
||||
@@ -0,0 +1,19 @@
|
||||
from fastapi import Request,Response
|
||||
import time
|
||||
|
||||
|
||||
black_list = ['192.168.1.40','192.168.1.94'] # '127.0.0.1','192.168.1.26‘
|
||||
# @app.middleware("http",)
|
||||
async def black(req:Request, next):
|
||||
if req.client.host in black_list:
|
||||
return Response("you are limited to access!")
|
||||
res = await next(req)
|
||||
# print('exite http',req.client.host)
|
||||
return res
|
||||
|
||||
# @app.middleware("https")
|
||||
async def time_(req:Request, next):
|
||||
print('enter https',time.time())
|
||||
res = await next(req)
|
||||
print('exite https',time.time())
|
||||
return res
|
||||
@@ -0,0 +1,56 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
from sqlalchemy.orm import declarative_base,sessionmaker
|
||||
from sqlalchemy import Column, Integer,String,DATETIME,create_engine
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
db_url = "mysql+pymysql://geeker:geeker@127.0.0.1:3306/fastapi_tutor?charset=utf8mb4"
|
||||
engine = create_engine(db_url)
|
||||
|
||||
|
||||
def get_session():
|
||||
Session = sessionmaker(bind=engine
|
||||
, autoflush=False
|
||||
, autocommit=False
|
||||
)
|
||||
yield Session()
|
||||
|
||||
# 定义创建订单的请求体模型
|
||||
class OrderRequest(BaseModel):
|
||||
id:int|None = None
|
||||
title:str|None = None
|
||||
userid:int|None =None
|
||||
|
||||
class OrderResponse(BaseModel):
|
||||
id:int|None = None
|
||||
title:str|None = None
|
||||
userid:int|None =None
|
||||
create_date:datetime|None = datetime.now()
|
||||
update_date:datetime|None = datetime.now()
|
||||
status_code:str|None = 200
|
||||
detail:str|None = 'succeed!'
|
||||
|
||||
|
||||
Base = declarative_base() # 执行函数,返回一个基类
|
||||
class Order(Base): # python里的表名字
|
||||
__tablename__ = 'order_info_detail' # 实际数据库的表名字
|
||||
id = Column( Integer # 声明字段的数据类型
|
||||
, primary_key=True # 声明是主键
|
||||
, autoincrement=True # 声明是自增主键
|
||||
, comment='订单编号,自增主键' # 注释
|
||||
)
|
||||
title = Column( String(50) # 对应数据库的varchar类型
|
||||
,nullable=False # 不允许为null
|
||||
)
|
||||
userid = Column( Integer
|
||||
# ,ForeignKey('user_info_detail.id')
|
||||
,nullable=False ) # 外键约束
|
||||
create_date = Column(DATETIME
|
||||
, default=datetime.now # 默认值是当前时间
|
||||
)
|
||||
update_date = Column(DATETIME
|
||||
, default=datetime.now # 首次创建的时间跟更新时间一致
|
||||
, onupdate=datetime.now
|
||||
)
|
||||
Order.__table__.create(engine,checkfirst = True)
|
||||
@@ -0,0 +1,9 @@
|
||||
[project]
|
||||
name = "fastapi-tutor"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = [
|
||||
"fastapi[standard]>=0.141.1",
|
||||
"pymysql>=1.2.3",
|
||||
"sqlalchemy>=2.0.54",
|
||||
]
|
||||
@@ -0,0 +1,73 @@
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy import create_engine, Column, Integer,String,DATETIME,DECIMAL
|
||||
from sqlalchemy.orm import declarative_base, sessionmaker
|
||||
from datetime import datetime
|
||||
|
||||
from typing import Annotated
|
||||
from fastapi import Depends, FastAPI,APIRouter,Form,File,UploadFile
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
|
||||
route = APIRouter(prefix="/mysql",tags=["mysql"])
|
||||
|
||||
db_url = "mysql+pymysql://geeker:geeker@localhost:3306/fastapi_tutor?charset=utf8mb4"
|
||||
engine = create_engine(db_url, pool_size= 50, echo=True)
|
||||
Session = sessionmaker( bind=engine
|
||||
,autoflush=False # 是否开启自动刷新python新增的数据到实际的数据库内存缓存中
|
||||
,autocommit = False # 不自动提交数据,这个是真正提交到数据库磁盘永久生效的 -> 2.0 不支持True自动提交
|
||||
)
|
||||
|
||||
session = Session() # 实例化一个数据库操作会话
|
||||
|
||||
|
||||
Base = declarative_base() # 获取基类!
|
||||
|
||||
class Order(Base): # python里的表名字
|
||||
__tablename__ = 'order_info_detail' # 实际数据库的表名字
|
||||
id = Column( Integer # 声明字段的数据类型
|
||||
, primary_key=True # 声明是主键
|
||||
, autoincrement=True # 声明是自增主键
|
||||
, comment='订单编号,自增主键' # 注释
|
||||
)
|
||||
title = Column( String(50) # 对应数据库的varchar类型
|
||||
,nullable=False # 不允许为null
|
||||
)
|
||||
price = Column(DECIMAL)
|
||||
userid = Column( Integer
|
||||
# ,ForeignKey('user_info_detail.id')
|
||||
,nullable=False ) # 外键约束
|
||||
create_date = Column(DATETIME
|
||||
, default=datetime.now # 默认值是当前时间
|
||||
)
|
||||
update_date = Column(DATETIME
|
||||
, default=datetime.now # 首次创建的时间跟更新时间一致
|
||||
, onupdate=datetime.now
|
||||
)
|
||||
|
||||
class User(Base): # python里的表名字
|
||||
__tablename__ = 'user_info_detail' # 实际数据库的表名字
|
||||
id = Column( Integer # 声明字段的数据类型
|
||||
, primary_key=True # 声明是主键
|
||||
, autoincrement=True # 声明是自增主键
|
||||
, comment='用户编号,自增主键' # 注释
|
||||
)
|
||||
username = Column( String(50) # 对应数据库的varchar类型
|
||||
,nullable=False # 不允许为null
|
||||
,comment='用户名'
|
||||
)
|
||||
create_date = Column(DATETIME
|
||||
, default=datetime.now # 默认值是当前时间
|
||||
)
|
||||
update_date = Column(DATETIME
|
||||
, default=datetime.now # 首次创建的时间跟更新时间一致
|
||||
, onupdate=datetime.now
|
||||
)
|
||||
|
||||
|
||||
@route.get("/user")
|
||||
def user_detail_info():
|
||||
return session.query(User).all()
|
||||
|
||||
@route.post("/user")
|
||||
def user_detail_info(username:str):
|
||||
session.add(User(username=username))
|
||||
return session.query(User).all()
|
||||
@@ -0,0 +1,51 @@
|
||||
# 编写更订单相关的增删改查接口,结合数据库
|
||||
from fastapi import FastAPI, APIRouter, Depends
|
||||
from sqlalchemy import *
|
||||
from sqlalchemy.orm import declarative_base,sessionmaker
|
||||
from datetime import datetime
|
||||
from pydantic import BaseModel
|
||||
|
||||
from model.order import OrderRequest,Order,get_session,OrderResponse
|
||||
|
||||
route = APIRouter(prefix="/order",tags=["orders"])
|
||||
|
||||
# 模糊查询
|
||||
@route.get('/',response_model=list[OrderResponse])
|
||||
def orders(id:int=None, user_id:int=None, title:str=None,session=Depends(get_session)):
|
||||
q = session.query(Order)
|
||||
if id: q = q.filter( Order.id == id )
|
||||
if user_id: q = q.filter( Order.userid == userid )
|
||||
if title: q = q.where(Order.title.like('%'+title+'%'))
|
||||
r = q.all()
|
||||
session.close()
|
||||
return r
|
||||
|
||||
@route.get('/{id}',response_model=OrderResponse)
|
||||
def orders(id:int):
|
||||
session = Session()
|
||||
r = session.query(Order).filter( Order.id == id ).all()
|
||||
session.close()
|
||||
return [ {"order_id":i.id,'order_title':i.title,"user_id":i.userid,"create_date":i.create_date,"update_date":i.update_date} for i in r]
|
||||
|
||||
@route.delete('/',response_model=OrderResponse)
|
||||
def orders(session=Depends(get_session)):
|
||||
return '删除订单'
|
||||
|
||||
|
||||
@route.post('/',response_model=OrderResponse)
|
||||
def orders(order:OrderRequest,session=Depends(get_session)):
|
||||
d = order.model_dump() # d = {"title":xxx,"userid":xxx}
|
||||
o1 = Order( **d )
|
||||
session.add(o1)
|
||||
session.commit()
|
||||
session.close()
|
||||
return {'code':200,'detail':'添加成功!'}
|
||||
|
||||
|
||||
@route.put('/{id}',response_model=OrderResponse)
|
||||
def orders(order:OrderRequest,id:int,session=Depends(get_session)):
|
||||
session.query( Order ).filter( Order.id == id ).update( order.model_dump(exclude_unset=True) )
|
||||
session.commit()
|
||||
res = session.query(Order).filter(Order.id == id).first()
|
||||
session.close()
|
||||
return res
|
||||
@@ -0,0 +1,23 @@
|
||||
from typing import Annotated
|
||||
from fastapi import Depends, FastAPI,APIRouter,Form,File,UploadFile
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
|
||||
route = APIRouter(prefix="/file",tags=["file"])
|
||||
|
||||
@route.post("/bytes")
|
||||
def bytes(file:bytes = File()):
|
||||
print("bytes:\t" + str(file))
|
||||
print("str:\t" + file.decode("utf-8"))
|
||||
return file.decode("utf-8")
|
||||
|
||||
|
||||
@route.post("/uploadfile")
|
||||
async def uploadfile(file:UploadFile):
|
||||
data = await file.read()
|
||||
print("bytes:\t" + str(data))
|
||||
print("str:\t" + data.decode("utf-8"))
|
||||
return data.decode("utf-8")
|
||||
|
||||
# text read/write
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
from typing import Annotated
|
||||
from fastapi import Depends, FastAPI,APIRouter,Form,Request,Response
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
|
||||
route = APIRouter(prefix="/form",tags=["form"])
|
||||
|
||||
@route.post("/token")
|
||||
def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]):
|
||||
# form_data 是解析后的表单对象
|
||||
# form_data.username → OAuth2 规范要求字段名必须是 "username"
|
||||
# form_data.password → OAuth2 规范要求字段名必须是 "password"
|
||||
# form_data.grant_type → 密码流程中必须是 "password"
|
||||
# form_data.scopes → 空格分隔的权限列表
|
||||
# form_data.client_id / client_secret → 如果客户端选择 client_secret_post
|
||||
return {
|
||||
"username": form_data.username,
|
||||
"password":form_data.password,
|
||||
"client_id": form_data.client_id,
|
||||
"client_secret":form_data.client_secret,
|
||||
"grant_type":form_data.grant_type,
|
||||
"scopes":form_data.scopes,
|
||||
}
|
||||
|
||||
|
||||
@route.post("/form")
|
||||
def login(username:str=Form(),password:str=Form()):
|
||||
# form_data 是解nt_id / client_secret → 如果客户端选择 client_secret_post
|
||||
return {
|
||||
"username": username,
|
||||
"password":password,
|
||||
}
|
||||
|
||||
@route.post("/query")
|
||||
def login(username:str,password:str):
|
||||
# form_data 是解nt_id / client_secret → 如果客户端选择 client_secret_post
|
||||
return {
|
||||
"username": username,
|
||||
"password":password,
|
||||
}
|
||||
|
||||
|
||||
@route.post("/raw")
|
||||
async def upload(request: Request):
|
||||
raw = await request.body() # bytes
|
||||
text = raw.decode("utf-8") # 明确解码
|
||||
return text
|
||||
@@ -0,0 +1,10 @@
|
||||
from typing import Annotated
|
||||
from fastapi import Depends, FastAPI,APIRouter,Form,File,UploadFile
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
|
||||
route = APIRouter(prefix="/json",tags=["json"])
|
||||
|
||||
|
||||
@route.get("/")
|
||||
def json():
|
||||
return [1,"str",[1,2,3],{1,2,3},{"a":1,"b":2}]
|
||||
@@ -0,0 +1,171 @@
|
||||
import fastapi
|
||||
from fastapi import APIRouter,Query,Path,Header,Cookie,File,UploadFile,Form,Request,Response,Depends
|
||||
from pydantic import BaseModel,Field
|
||||
from typing import Union,Optional,Literal,Annotated
|
||||
|
||||
|
||||
route = APIRouter(prefix="/methods",tags=["methods"])
|
||||
|
||||
'''
|
||||
Filed & Query : 做param validate!
|
||||
'''
|
||||
class Item(BaseModel):
|
||||
name: str
|
||||
description: str | None = None
|
||||
price: float
|
||||
tax: float | None = None
|
||||
|
||||
class StuUpdate(BaseModel):
|
||||
id : int|None = None
|
||||
name:str|None = None
|
||||
age:int = None
|
||||
sex:Literal['男','女']|None = None
|
||||
address:str|None = None
|
||||
|
||||
|
||||
@route.put('/model_dump/{sid}')
|
||||
def model_dump(sid:int,s:StuUpdate):
|
||||
d = s.model_dump(exclude_unset=True)
|
||||
return d
|
||||
|
||||
@route.post("/model_dump/")
|
||||
async def create_item(item: Item):
|
||||
item_dict = item.model_dump()
|
||||
if item.tax is not None:
|
||||
price_with_tax = item.price + item.tax
|
||||
item_dict.update({"price_with_tax": price_with_tax})
|
||||
return item_dict
|
||||
|
||||
@route.put("/body_path/{item_id}")
|
||||
async def update_item(item_id: int, item: Item):
|
||||
return {"item_id": item_id, **item.model_dump()}
|
||||
|
||||
@route.put("/body_path_query/{item_id}")
|
||||
async def update_item(item_id: int, item: Item, q: str | None = None):
|
||||
result = {"item_id": item_id, **item.model_dump()}
|
||||
if q:
|
||||
result.update({"q": q})
|
||||
return result
|
||||
|
||||
|
||||
class Body(BaseModel):
|
||||
id: int = 1 # :
|
||||
name: str = Field(default="good", min_length=2, max_length=20,description="test in class defination name")
|
||||
age: int = Field(default=18,ge=1,le=100)
|
||||
isNone: bool | None = False
|
||||
lst: list[int|str|BaseModel]
|
||||
option: Literal['a','b']
|
||||
|
||||
bodies = [{"id":1,"name":"test"},]
|
||||
|
||||
@route.post("/body_default")
|
||||
async def post(body:Body) ->list: # 500
|
||||
bodies.append(body)
|
||||
return bodies
|
||||
|
||||
@route.post("/body_fastapi.Body")
|
||||
async def body_fastapi_Body(body: Body = fastapi.Body()) ->list: # 500
|
||||
return bodies
|
||||
|
||||
@route.post("/body_fastapi.query")
|
||||
async def body_fastapi_query(body: Body = fastapi.Query()) ->list: # 500
|
||||
return bodies
|
||||
|
||||
|
||||
@route.get("debug/{item_id}")
|
||||
async def read_item(item_id: str = Path(min_length=2,max_length=100,description="test str in path variables")
|
||||
, q: str | None = Query(min_length=2)):
|
||||
if q:
|
||||
return {"item_id": item_id, "q": q}
|
||||
return {"item_id": item_id}
|
||||
|
||||
|
||||
@route.get("debugs/{item_id}")
|
||||
async def read_item(item_id: Optional[str], q: str | None = None, short: bool = False):
|
||||
item = {"item_id": item_id}
|
||||
if q:
|
||||
item.update({"q": q})
|
||||
if not short:
|
||||
item.update(
|
||||
{"description": "This is an amazing item that has a long description"}
|
||||
)
|
||||
return item
|
||||
|
||||
@route.get("debugs1/{item_id}")
|
||||
async def read_item(item_id: Union[str,int], q: str | None = None, short: bool = False):
|
||||
item = {"item_id": item_id}
|
||||
if q:
|
||||
item.update({"q": q})
|
||||
if not short:
|
||||
item.update(
|
||||
{"description": "This is an amazing item that has a long description"}
|
||||
)
|
||||
return item
|
||||
|
||||
|
||||
@route.get("/users/{user_id}/items/{item_id}",tags=['users'])
|
||||
async def read_user_item(
|
||||
user_id: int, item_id: str, q: str | None = None, short: bool = False
|
||||
):
|
||||
item = {"item_id": item_id, "owner_id": user_id}
|
||||
if q:
|
||||
item.update({"q": q})
|
||||
if not short:
|
||||
item.update(
|
||||
{"description": "This is an amazing item that has a long description"}
|
||||
)
|
||||
return item
|
||||
|
||||
|
||||
|
||||
@route.api_route("/multi", methods=["GET", "POST", "PUT"],summary="summary",description="description",tags=['multi'])
|
||||
def multi():
|
||||
return {"message": "支持多种方法"}
|
||||
|
||||
|
||||
@route.api_route("/trace", methods=["TRACE"])
|
||||
def trace_endpoint():
|
||||
return {"method": "TRACE"}
|
||||
|
||||
|
||||
@route.api_route("/connect", methods=["CONNECT"])
|
||||
def connect_endpoint():
|
||||
return {"method": "CONNECT"}
|
||||
|
||||
|
||||
from fastapi import WebSocket
|
||||
from starlette.websockets import WebSocketDisconnect
|
||||
@route.websocket("/ws")
|
||||
async def websocket_endpoint(websocket: WebSocket):
|
||||
# await websocket.accept()
|
||||
# while True:
|
||||
# data = await websocket.receive_text()
|
||||
# await websocket.send_text(f"收到: {data}")
|
||||
await websocket.accept()
|
||||
try:
|
||||
while True:
|
||||
data = await websocket.receive_text()
|
||||
await websocket.send_text(f"Message: {data}")
|
||||
except WebSocketDisconnect:
|
||||
print("客户端断开连接")
|
||||
finally:
|
||||
# 清理资源
|
||||
pass
|
||||
|
||||
|
||||
|
||||
def ori_func(e):
|
||||
return e
|
||||
|
||||
def ori_func1(a,b,c,d,z=Depends(ori_func)):
|
||||
return ori_func
|
||||
|
||||
@route.get("/depends")
|
||||
def depends(req: Request, r=Depends(ori_func1)):
|
||||
# req
|
||||
return r
|
||||
|
||||
@route.get("/depends_nested")
|
||||
def depends_nested(req: Request, r=Depends(depends)):
|
||||
# req
|
||||
return r
|
||||
@@ -0,0 +1,235 @@
|
||||
from fastapi import APIRouter, Request,Header,Cookie
|
||||
|
||||
from pydantic import constr,Field, field_validator
|
||||
from typing import Annotated
|
||||
from fastapi import Path,Query, Form,File,UploadFile
|
||||
|
||||
from router.features import method
|
||||
|
||||
route = APIRouter(prefix="/mimetypes",tags=["mimetypes"])
|
||||
|
||||
from uuid import UUID
|
||||
from pydantic import BaseModel, EmailStr, HttpUrl, IPvAnyAddress, conint
|
||||
|
||||
@route.get("/any_types")
|
||||
def any_types(email: EmailStr
|
||||
, url: HttpUrl = Query()
|
||||
, ipv4: IPvAnyAddress | None=None
|
||||
, ipv6: IPvAnyAddress | None=None
|
||||
, uuid: UUID | None=None
|
||||
# , date: date
|
||||
# , datetime: datetime
|
||||
# , time: time
|
||||
# , timedelta: timedelta
|
||||
# , decimal: Decimal
|
||||
# , json: Json
|
||||
# , secret: SecretStr
|
||||
# , secret_bytes: SecretBytes
|
||||
# , conint: conint(gt=0, lt=100)
|
||||
):
|
||||
return {
|
||||
"email": email,
|
||||
"url": url,
|
||||
"ipv4": ipv4,
|
||||
"ipv6": ipv6,
|
||||
"uuid": uuid
|
||||
}
|
||||
pass
|
||||
|
||||
|
||||
'''
|
||||
form/ file
|
||||
'''
|
||||
|
||||
@route.post('/login')
|
||||
def login(name:str = Form()):
|
||||
return name
|
||||
|
||||
|
||||
@route.post('/upload')
|
||||
def upload(file_name:bytes = File()):
|
||||
return file_name
|
||||
|
||||
@route.post("/upload_raw")
|
||||
async def upload(request: Request):
|
||||
return await request.body()
|
||||
|
||||
@route.post('/upload_files')
|
||||
def upload(file_name:list[bytes] = File()):
|
||||
return file_name
|
||||
|
||||
|
||||
@route.post('/upload_files_annotated')
|
||||
def upload(file_name:Annotated[list[bytes],File()]):
|
||||
return file_name
|
||||
|
||||
|
||||
@route.post('/upload_uploadfiles')
|
||||
def upload(files:list[UploadFile] = File()):
|
||||
return [f.filename for f in files]
|
||||
|
||||
@route.post('/upload_file')
|
||||
def upload(file:UploadFile):
|
||||
return file
|
||||
|
||||
|
||||
@route.post('/upload_str')
|
||||
def upload(file_name:str = File()):
|
||||
return file_name
|
||||
|
||||
|
||||
'''
|
||||
validator app
|
||||
'''
|
||||
@route.get("/validate_con")
|
||||
def web(
|
||||
age: conint(ge=0, lt=150),
|
||||
name: constr(min_length=1, max_length=50),
|
||||
):
|
||||
return {
|
||||
"age":age,
|
||||
"name":name
|
||||
}
|
||||
|
||||
@route.get("/validate_field")
|
||||
def web(
|
||||
num:int = Query(Field(10,le=100,ge=8))
|
||||
):
|
||||
return num
|
||||
|
||||
|
||||
@route.get("/validate_annotated")
|
||||
def web(
|
||||
age: Annotated[int, Field(ge=0, lt=150)],
|
||||
name: Annotated[str, Field(min_length=1, max_length=50)],
|
||||
):
|
||||
return {
|
||||
"age": age,
|
||||
"name": name
|
||||
}
|
||||
|
||||
|
||||
@route.get("/validated_annotated_query")
|
||||
async def get(option: Annotated[str|None, Query(max_length=10)] = None):
|
||||
return option
|
||||
|
||||
@route.get("/validated_annotated_path/{option}")
|
||||
async def get(option: Annotated[str|None, Path(max_length=10)]):
|
||||
return option
|
||||
|
||||
'''
|
||||
|
||||
'''
|
||||
class ValidatorSelf(BaseModel):
|
||||
email: EmailStr
|
||||
age: int
|
||||
|
||||
@field_validator("age")
|
||||
def age_must_be_realistic(cls, v):
|
||||
if v > 150:
|
||||
raise ValueError("age too large")
|
||||
return v
|
||||
|
||||
|
||||
@route.post("/validated_self")
|
||||
async def post(vs: ValidatorSelf):
|
||||
return vs
|
||||
|
||||
|
||||
@route.get("/request")
|
||||
def request(res: Request):
|
||||
return res.headers.raw
|
||||
|
||||
|
||||
from fastapi import Request
|
||||
# from method import Item
|
||||
# @route.api_route("/anything/{item}", methods=["GET", "POST", "PUT", "PATCH", "DELETE"])
|
||||
async def anything(request: Request):
|
||||
return {
|
||||
"method": request.method,
|
||||
"path_params": request.path_params,
|
||||
"query": dict(request.query_params),
|
||||
"headers": dict(request.headers),
|
||||
"cookies": request.cookies,
|
||||
"body": (await request.body()).decode("utf-8", "ignore"),
|
||||
}
|
||||
|
||||
@route.post("/anything_params/{item_id}")
|
||||
async def anything_params(
|
||||
item_id: Annotated[int, Path()],
|
||||
dry_run: Annotated[bool, Query()] = False,
|
||||
x_token: Annotated[str, Header()] = None,
|
||||
session: Annotated[str | None, Cookie()] = None,
|
||||
# item: Item | None = None, # JSON body & multi-part cannot occured same time!
|
||||
item: str = Form(),
|
||||
avatar: UploadFile | None = File(None)):
|
||||
|
||||
import json
|
||||
item_obj = method.Item.model_validate(json.loads(item))
|
||||
return {
|
||||
"item_id": item_id,
|
||||
"dry_run": dry_run,
|
||||
"x_token": x_token,
|
||||
"session": session,
|
||||
"item": json.dumps(item_obj),
|
||||
"avatar": avatar
|
||||
}
|
||||
|
||||
|
||||
@route.get("/proxy")
|
||||
def get(request: Request):
|
||||
path = request.url_for('sn',path = 'head.png')
|
||||
return path
|
||||
|
||||
|
||||
# 定义请求体模型
|
||||
class UserRequest(BaseModel):
|
||||
username:str
|
||||
passwd:str
|
||||
|
||||
class UserResponse(BaseModel):
|
||||
code:int = 200
|
||||
detail:str = 'ok'
|
||||
totals:int = 0
|
||||
username:str
|
||||
|
||||
@route.post('/user',response_model=UserResponse)
|
||||
def user(u:UserRequest):
|
||||
# return UserResponse(username=u.username)
|
||||
return u # 等价于 return UserResponse(username=u.username)
|
||||
|
||||
@route.post('/users')
|
||||
def users(u:list[UserRequest]):
|
||||
x = [_.username for _ in u]
|
||||
return UserResponse(code='200',totals = len(x),username=x[0])
|
||||
|
||||
|
||||
@route.post('/users_list')
|
||||
def users(u:list[str]):
|
||||
return UserResponse(code='200',totals = len(u),username=u[0])
|
||||
|
||||
@route.get('/url_for/{id}',name='url_for')
|
||||
def url_for(id:int):
|
||||
return id
|
||||
|
||||
@route.get('/test_url_for')
|
||||
def url_for(res:Request):
|
||||
url = res.url_for('url_for',id = 123)
|
||||
return {"url":str(url)}
|
||||
|
||||
|
||||
from fastapi import status,Response
|
||||
|
||||
|
||||
@route.post("/status_code", status_code=status.HTTP_201_CREATED)
|
||||
async def status_code(name:str):
|
||||
return name
|
||||
|
||||
# 或者动态设置
|
||||
@route.post("/response")
|
||||
async def response(name: str, response: Response):
|
||||
response.status_code = 201
|
||||
response.headers["X-Custom"] = "value"
|
||||
return name
|
||||
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
# from idlelib.query import Query
|
||||
from typing import Optional
|
||||
from fastapi import APIRouter,Query
|
||||
from pydantic import BaseModel
|
||||
import re
|
||||
|
||||
from enum import Enum
|
||||
|
||||
route = APIRouter(prefix="/path_query_variables",tags=["path_query_variables"])
|
||||
|
||||
stu = [{"sid":'001','name':'gk','age':18,'address':'dd_abc_lg_', 'sex':'male'},
|
||||
{"sid":'002','name':'gk2','age':19},
|
||||
{"sid":'003','name':'gk3','age':20}]
|
||||
|
||||
class Sex(str,Enum):
|
||||
MALE = 1
|
||||
FEMAIL = 2,
|
||||
TRANS = 3
|
||||
|
||||
maile = '男'
|
||||
femail = '女',
|
||||
trans = '跨性别'
|
||||
|
||||
|
||||
|
||||
@route.get("/{sid}")
|
||||
def get(sid: str):
|
||||
for s in stu:
|
||||
if s['sid'] == sid: return s
|
||||
return None
|
||||
|
||||
|
||||
@route.get("/")
|
||||
def get(age_min: int | None = None, age_max: int| None=None):
|
||||
lst = []
|
||||
for s in stu:
|
||||
if age_min <= s['age'] <= age_max: lst.append(s)
|
||||
return lst
|
||||
|
||||
# todo: not support int|None? <0.141.1
|
||||
'''
|
||||
BP:
|
||||
- Optional[str,None]
|
||||
- str | None
|
||||
- str | None = None
|
||||
|
||||
'''
|
||||
@route.get("/validate")
|
||||
def get(age_min: int=Query(default=0, ge=0,le=100),
|
||||
age_max: int=Query(default=0, ge=0,le=100),
|
||||
sname:str=Query(default='',min_length=2,max_length=10),
|
||||
address:str=Query(pattern=".*lg.*"),
|
||||
sex:Sex=Query()
|
||||
):
|
||||
|
||||
return [x for x in stu if age_min <= x['age'] <= age_max and x['name']==sname and re.search(address,x['address'])]
|
||||
|
||||
|
||||
|
||||
# path
|
||||
@route.get("/good/{path:path}")
|
||||
def get_path(path: str):
|
||||
return path
|
||||
|
||||
@route.get("/bad/{path}")
|
||||
def get_path(path: str):
|
||||
return path
|
||||
|
||||
class Object(BaseModel):
|
||||
id:int|None = None
|
||||
name: str|None = None
|
||||
price: float|None = None
|
||||
|
||||
|
||||
@route.post("/model_dump_extraset")
|
||||
def model_dump_extraset(obj: Object):
|
||||
return obj.model_dump(exclude_unset=True)
|
||||
|
||||
|
||||
@route.get("/model_dump_extraset")
|
||||
def model_dump_extraset(obj: Object):
|
||||
return obj.model_dump(exclude_unset=True)
|
||||
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
import fastapi
|
||||
from fastapi import APIRouter,Query,Path,HTTPException
|
||||
from pydantic import Field,BaseModel,field_validator,model_validator
|
||||
from typing import Annotated,Union,Optional
|
||||
|
||||
|
||||
route = APIRouter(prefix="/validations",tags=["validations"])
|
||||
|
||||
@route.get("/annotated_query")
|
||||
async def get(option: Annotated[str|None, Query(max_length=10)] = None):
|
||||
return option
|
||||
|
||||
@route.get("/annotated_query_lst")
|
||||
async def get(option: Annotated[list[str], Query()] = None):
|
||||
return option
|
||||
|
||||
|
||||
@route.get("/annotated_query_re")
|
||||
async def get(option: Annotated[str|None, Query(min_length=2,max_length=10,pattern="^g.*k$")] = None):
|
||||
return option
|
||||
|
||||
|
||||
@route.get("/annotated_path/{option}")
|
||||
async def get(option: Annotated[str|None, Path(max_length=10)]):
|
||||
return option
|
||||
|
||||
|
||||
@route.get("/query")
|
||||
async def get(option: str = Query(min_length=2, max_length=10)):
|
||||
return option
|
||||
|
||||
@route.get("/params_validate")
|
||||
async def params_validate(name:Union[str,None], # default
|
||||
id: Optional[int]):
|
||||
pass
|
||||
|
||||
|
||||
# @route.get("/field")
|
||||
# async def get(option: str = Field(min_length=2, max_length=10)):
|
||||
# return option
|
||||
|
||||
class Clazz(BaseModel):
|
||||
name: str = Field(min_length=2,max_length=12)
|
||||
|
||||
@route.get("/field")
|
||||
async def get(clazz: Clazz):
|
||||
return clazz
|
||||
|
||||
@route.get("/field_body")
|
||||
async def get(clazz: Clazz = fastapi.Body()):
|
||||
return clazz
|
||||
|
||||
|
||||
@route.get("/path_path/{option}")
|
||||
async def get(option: str = Path(min_length=2, max_length=10)):
|
||||
return option
|
||||
#
|
||||
# @route.get("/path_query/{option}")
|
||||
# async def get(option: str = Query(min_length=2, max_length=10)):
|
||||
# return option
|
||||
|
||||
# param not got!
|
||||
@route.get("/path_bad")
|
||||
async def get(option: str = Path(min_length=2, max_length=10)):
|
||||
return option
|
||||
|
||||
|
||||
@route.get("/items/")
|
||||
async def read_items(q: str|None = Query(default=None, max_length=50)):
|
||||
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
|
||||
if q:
|
||||
results.update({"q": q})
|
||||
return results
|
||||
|
||||
@route.get("/items_default_bad/")
|
||||
async def read_items(q: str = Query(default=None, max_length=50)):
|
||||
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
|
||||
if q:
|
||||
results.update({"q": q})
|
||||
return results
|
||||
|
||||
|
||||
# field/model_validator
|
||||
|
||||
class User(BaseModel):
|
||||
name: str
|
||||
passwd: str
|
||||
|
||||
# @field_validator("passwd")
|
||||
# def check_passwd(cls,passwd,values): #
|
||||
# # print(f'{values=}') # values=ValidationInfo(config={'title': 'User'}, context=None, data={'name': 'string'}, field_name='passwd')
|
||||
# # 无法cannot access passwd/ name ?!
|
||||
# # print(f'{values["passwd"]=}')
|
||||
# # print(f'{values["name"]=}')
|
||||
# if passwd == '001':
|
||||
# raise HTTPException(status_code=408,detail="001 only root can set!")
|
||||
# return passwd
|
||||
|
||||
@model_validator(mode="before")
|
||||
def check_model_before(cls, data): #
|
||||
print('model_validator before............')
|
||||
return data
|
||||
|
||||
@field_validator("passwd","name")
|
||||
def check_field(cls,value,values): #
|
||||
# print(f'{values=}') # values=ValidationInfo(config={'title': 'User'}, context=None, data={'name': 'string'}, field_name='passwd')
|
||||
# cannot access passwd/ name ?!
|
||||
# print(f'{values["passwd"]=}')
|
||||
# print(f'{values["name"]=}')
|
||||
print('field_validator............')
|
||||
if value == '001':
|
||||
raise HTTPException(status_code=408,detail="001 only root can set!")
|
||||
if value == 'root':
|
||||
raise HTTPException(status_code=408,detail="only root can set name!")
|
||||
return value
|
||||
|
||||
@model_validator(mode="after")
|
||||
def check_model_after(self): #
|
||||
print('model_validator............')
|
||||
if self.passwd == '001':
|
||||
raise HTTPException(status_code=408,detail="model : 001 only root can set!")
|
||||
if self.name == 'root':
|
||||
raise HTTPException(status_code=408,detail="model : only root can set name!")
|
||||
return self
|
||||
|
||||
|
||||
# u = User(age="20", name="geeker")
|
||||
|
||||
|
||||
@route.post("/registe")
|
||||
def registe(user: User):
|
||||
return user
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
b
|
||||
@@ -0,0 +1 @@
|
||||
a
|
||||
@@ -0,0 +1 @@
|
||||
/home/geeker/architecture/home/geeker/code_repo/infrastructure_system_softwares/language_dev/python/fastapi_pycharm_proj/router/workhome/a
|
||||
@@ -0,0 +1 @@
|
||||
/home/geeker/architecture/home/geeker/code_repo/infrastructure_system_softwares/language_dev/python/fastapi_pycharm_proj/router/workhome/b
|
||||
@@ -0,0 +1,135 @@
|
||||
from fastapi import APIRouter,Query,Request,Path,Query,Body,Form,File,UploadFile
|
||||
from pydantic import constr,Field,field_validator,BaseModel, EmailStr, HttpUrl, IPvAnyAddress, conint
|
||||
from typing import Annotated,Literal
|
||||
|
||||
from datetime import date, timedelta
|
||||
import datetime
|
||||
from decimal import Decimal
|
||||
from time import time
|
||||
from uuid import UUID
|
||||
import json,os,logging
|
||||
|
||||
route = APIRouter(prefix="/wk0915",tags=["wk0915"])
|
||||
|
||||
class Login(BaseModel):
|
||||
username: str = Field(min_length=3, max_length=20,pattern="^[a-zA-Z]")
|
||||
passwd: str = Field(min_length=6, max_length=20)
|
||||
is_ok: bool
|
||||
|
||||
@route.post("/login/{session_id}")
|
||||
def login(session_id:str = Path(min_length=10, pattern="^[a-zA-Z0-9]+$"),
|
||||
usertype:int| None = None,
|
||||
login:Login =Form()):
|
||||
d = {
|
||||
"username":login.username,
|
||||
"usertype":usertype,
|
||||
"is_ok":login.is_ok
|
||||
}
|
||||
# lg = Login.model_validate(json.loads())
|
||||
# return {
|
||||
# "login": d
|
||||
# }
|
||||
return d
|
||||
|
||||
|
||||
@route.get("/user/{user_id}")
|
||||
def user(user_id: int = Path(gt=1)):
|
||||
return user_id
|
||||
|
||||
class Product(BaseModel):
|
||||
pname: str = Field(min_length=2,max_length=50)
|
||||
price: int = Field(gt=0)
|
||||
stock: int = Field(gt=0)
|
||||
type: str = Field(min_length=1) # todo not blank?
|
||||
|
||||
@route.post("/product")
|
||||
def product(product:Product):
|
||||
return product
|
||||
|
||||
stu = teac = []
|
||||
|
||||
class Student(BaseModel):
|
||||
sid: int | None = None
|
||||
sname: str
|
||||
age: int | None = Field(default=None, ge=0)
|
||||
sex: Literal['男','女']
|
||||
hobby: set[str]
|
||||
create_date: datetime.datetime = Field(default_factory=datetime.datetime.now)
|
||||
|
||||
|
||||
@route.post("/student")
|
||||
def student(student: Student):
|
||||
stu.append(student)
|
||||
return student
|
||||
|
||||
|
||||
@route.get('/student')
|
||||
def student(sname:str|None = Query(default=None),
|
||||
sex:str|None = Query(default=None), # todo Literal validate
|
||||
age:int|None = Query(default=None, ge=0, le=120)
|
||||
):
|
||||
|
||||
# res = [s for s in stu if s['sname'] == sname]
|
||||
if sname == None and sex == None and age == None : return stu
|
||||
if sname != None and sex != None and age != None: return [s for s in stu if (sname != None and s.sname == sname)
|
||||
and (sex != None and s.sex == sex)
|
||||
and (age != None and s.age == age)]
|
||||
if sname != None and sex != None : return [s for s in stu if (sname != None and s.sname == sname)
|
||||
and (sex != None and s.sex == sex)]
|
||||
if sname != None and age != None : return [s for s in stu if (sname != None and s.sname == sname)
|
||||
and (age != None and s.age == age)]
|
||||
if sex != None and age != None : return [s for s in stu if (sex != None and s.sex == sex)
|
||||
and (age != None and s.age == age)]
|
||||
if sex != None : return [s for s in stu if s.sex == sex]
|
||||
if sname != None : return [s for s in stu if s.sname == sname]
|
||||
if age != None : return [s for s in stu if s.age == age]
|
||||
|
||||
|
||||
class Teacher(BaseModel):
|
||||
name: str|None
|
||||
salary: float
|
||||
hiredate: date
|
||||
stu: list[Student]
|
||||
|
||||
|
||||
@route.post("/teacher")
|
||||
def teacher(teacher:Teacher):
|
||||
teac.append(teacher)
|
||||
return teac
|
||||
|
||||
import random
|
||||
@route.post("/upload")
|
||||
def upload(files:list[bytes]= File()):
|
||||
# names = random.choices(['001','002','geeker'],len(files)) # todo duplacate names
|
||||
# names = random.choices(range(len(files)),len(files)) # set fix!
|
||||
i = 0 # order name
|
||||
saved = []
|
||||
for file in files:
|
||||
try:
|
||||
# filepath = os.path.join(os.getcwd(), i)
|
||||
filepath = os.getcwd() + "/router/workhome/" + str(i)
|
||||
with open(filepath,'wb') as f:
|
||||
f.write(file)
|
||||
i += 1
|
||||
saved.append(filepath)
|
||||
except Exception: # exception too large!
|
||||
pass
|
||||
|
||||
return saved
|
||||
|
||||
|
||||
@route.post("/upload_file")
|
||||
async def upload(files:list[UploadFile] = File()):
|
||||
saved = []
|
||||
for file in files:
|
||||
path = os.getcwd() + "/router/workhome/" + file.filename
|
||||
data = await file.read()
|
||||
|
||||
try:
|
||||
with open(path,'w',encoding='utf-8') as f:
|
||||
f.write(data)
|
||||
saved.append(path)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return saved
|
||||
@@ -0,0 +1,86 @@
|
||||
from fastapi import APIRouter,Query,Request,Path,Query,Body,Form,File,UploadFile,HTTPException
|
||||
from pydantic import constr,Field,field_validator,BaseModel, EmailStr, HttpUrl, IPvAnyAddress, conint
|
||||
from typing import Annotated,Literal
|
||||
|
||||
from datetime import date, timedelta
|
||||
import datetime
|
||||
from decimal import Decimal
|
||||
from time import time
|
||||
from uuid import UUID
|
||||
import json,os,logging
|
||||
from hashlib import md5 # ①导入md5加密
|
||||
from libplus.logpluslplus import Log
|
||||
|
||||
route = APIRouter(prefix="/wk0916",tags=["wk0916"])
|
||||
|
||||
users = []
|
||||
|
||||
@route.post("/registe")
|
||||
def registe(username:str, passwd:str):
|
||||
users.append({"username":username,"passwd":passwd})
|
||||
return users
|
||||
|
||||
|
||||
@route.post("upload")
|
||||
async def upload(file:UploadFile,res:Request):
|
||||
data = await file.read()
|
||||
if "洗脚" in data:
|
||||
md = md5()
|
||||
md.update(("sensitive_word" + str(range(10))).encode())
|
||||
file_name = md.hexdigest()
|
||||
with open(file_name,'w',encoding='utf-8') as f:
|
||||
f.write(data)
|
||||
log = Log('local_log','warning')
|
||||
log.set_file_logger("logfile.log")
|
||||
log.warning("洗脚"+res.client.host)
|
||||
else:
|
||||
log = Log('local_log', 'info')
|
||||
log.get_file_logger("logfile.log")
|
||||
log.warning("no洗脚" + res.client.host)
|
||||
return "该文件没有敏感词汇"
|
||||
|
||||
|
||||
stu = [{"sname":"gk","classid":"java"},
|
||||
{"sname":"yb","classid":"python"}]
|
||||
|
||||
|
||||
@route.get("/student")
|
||||
def student(name:str):
|
||||
for s in stu:
|
||||
if s["sname"] == name:
|
||||
return s
|
||||
raise HTTPException(status_code=404,detail="学生不存在")
|
||||
|
||||
# app.mount(path="/staticfile",app=StaticFiles(directory="d://tmp"),name="sn")
|
||||
|
||||
class Response(BaseModel):
|
||||
code: int = 200
|
||||
detail: str = "添加成功"
|
||||
total: int = 1
|
||||
sname: str
|
||||
classid: int
|
||||
|
||||
|
||||
class Request(BaseModel):
|
||||
sname: str
|
||||
classid: int
|
||||
|
||||
@route.get("/student_model",response_model=Response)
|
||||
def student_model(s:Request):
|
||||
for s0 in stu:
|
||||
if s0["sname"] == s.sname:
|
||||
raise HTTPException(status_code=404,detail="学生不存在")
|
||||
return s
|
||||
|
||||
|
||||
@route.post("/student_model_post",response_model=Response)
|
||||
def student_model_post(s:Request):
|
||||
stu.append({"sname":s.sname,"classid":s.classid})
|
||||
# suc / fail
|
||||
flag = True
|
||||
if flag : return Response(code = '200',sname=s.sname,classid = s.classid)
|
||||
else :return Response(code = '500',sname=s.sname,classid = s.classid)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
from fastapi import APIRouter,Query,Request,Path,Query,Body,Form,File,UploadFile,HTTPException,Response
|
||||
from pydantic import constr,Field,field_validator,BaseModel, EmailStr, HttpUrl, IPvAnyAddress, conint,model_validator
|
||||
from typing import Annotated,Literal
|
||||
|
||||
from datetime import date, timedelta
|
||||
import datetime
|
||||
from decimal import Decimal
|
||||
from time import time
|
||||
from uuid import UUID
|
||||
import json,os,logging
|
||||
from hashlib import md5 # ①导入md5加密
|
||||
from libplus.logpluslplus import Log
|
||||
|
||||
route = APIRouter(prefix="/wk0917",tags=["wk0917"])
|
||||
|
||||
products = [{"name":"a","price":123.9,"stock":2},{"name":"a","price":123.9,"stock":2},{"name":"a","price":123.9,"stock":2},]
|
||||
class Product(BaseModel):
|
||||
name:str
|
||||
price:float
|
||||
stock: int
|
||||
|
||||
@model_validator(mode='after') #
|
||||
def check(self):
|
||||
pnames = [p['name'] for p in products]
|
||||
if self.name in pnames: raise HTTPException(status_code="409",detail="name duplicate")
|
||||
return self
|
||||
|
||||
@route.get("/product")
|
||||
def product(name:str):
|
||||
for p in products:
|
||||
if name == p["name"]: return p
|
||||
else: return Response(status_code="409",content="not find!")
|
||||
|
||||
@route.post("/product")
|
||||
def product(p: Product):
|
||||
products.append(p.model_dump())
|
||||
return p
|
||||
|
||||
|
||||
orders = []
|
||||
@route.get("/order")
|
||||
def order(name:str):
|
||||
return name
|
||||
|
||||
@route.post("/order")
|
||||
def order(name:str):
|
||||
orders.append(name)
|
||||
return name
|
||||
|
||||
|
||||
Reference in New Issue
Block a user