From f0831731789c936ad3c210b42f699a50ac0ebdbc Mon Sep 17 00:00:00 2001
From: longsihua2003 <1161443436@qq.com>
Date: Mon, 6 May 2024 01:36:44 +0800
Subject: [PATCH 1/6] fastapi demo
---
ecjtu/ecjtu_api/api.py | 42 ++++++++++++++++++++
ecjtu/ecjtu_api/auth.py | 12 ++++++
ecjtu/ecjtu_api/ecjtu_schema.py | 5 +++
ecjtu/ecjtu_api/middle.py | 32 +++++++++++++++
ecjtu/ecjtu_api/respose_result.py | 66 +++++++++++++++++++++++++++++++
ecjtu/ecjtu_api/server.py | 18 +++++++++
ecjtu/server.py | 0
7 files changed, 175 insertions(+)
create mode 100644 ecjtu/ecjtu_api/api.py
create mode 100644 ecjtu/ecjtu_api/auth.py
create mode 100644 ecjtu/ecjtu_api/ecjtu_schema.py
create mode 100644 ecjtu/ecjtu_api/middle.py
create mode 100644 ecjtu/ecjtu_api/respose_result.py
create mode 100644 ecjtu/ecjtu_api/server.py
delete mode 100644 ecjtu/server.py
diff --git a/ecjtu/ecjtu_api/api.py b/ecjtu/ecjtu_api/api.py
new file mode 100644
index 0000000..30b7113
--- /dev/null
+++ b/ecjtu/ecjtu_api/api.py
@@ -0,0 +1,42 @@
+from fastapi import FastAPI, Depends,Header
+from fastapi.security import OAuth2PasswordRequestForm
+
+import middle
+from ecjtu_schema import UserSchema, UserLoginSchema
+from respose_result import ResponseResult
+from auth import encode,decode
+
+from ecjtu.client import ECJTU
+
+app = FastAPI(title="ECJTU API", description="API for ECJTU")
+
+app.add_middleware(middle.MyMiddleware)
+
+@app.post("/login")
+def login(user: UserLoginSchema):
+ client = ECJTU(user.stud_id, user.password)
+ try:
+ client.login()
+ except Exception as e:
+ return ResponseResult.error(str(e))
+ token = encode(user.stud_id, user.password)
+ return ResponseResult.success({"token": token})
+
+# gpa接口
+@app.get("/gpa")
+def gpa(token: str = Header(None)):
+ stud_id, password = decode(token)
+ client = ECJTU(stud_id, password)
+ try:
+ gpa = client.gpa.today()
+ except Exception as e:
+ return ResponseResult.error(str(e))
+ return ResponseResult.success(dict(gpa))
+
+# 启动api服务
+def start_api_server():
+ import uvicorn
+ uvicorn.run(app, host="127.0.0.1", port=8080)
+
+if __name__ == "__main__":
+ start_api_server()
\ No newline at end of file
diff --git a/ecjtu/ecjtu_api/auth.py b/ecjtu/ecjtu_api/auth.py
new file mode 100644
index 0000000..2c065df
--- /dev/null
+++ b/ecjtu/ecjtu_api/auth.py
@@ -0,0 +1,12 @@
+import base64
+# from ecjtu.client import _get_enc_password
+KEY = "zxcvbnmasdgfhjklpoiuytrewq"
+
+def encode(stud_id, pwd):
+ # enc_pwd = _get_enc_password(pwd);
+ token = base64.b64encode(f"{stud_id}:{pwd}".encode()).decode()
+ return token
+
+def decode(token):
+ stud_id, enc_pwd = base64.b64decode(token.encode()).decode().split(":")
+ return stud_id, enc_pwd
\ No newline at end of file
diff --git a/ecjtu/ecjtu_api/ecjtu_schema.py b/ecjtu/ecjtu_api/ecjtu_schema.py
new file mode 100644
index 0000000..1fb7bd9
--- /dev/null
+++ b/ecjtu/ecjtu_api/ecjtu_schema.py
@@ -0,0 +1,5 @@
+from pydantic import BaseModel, Field
+
+class UserLoginSchema(BaseModel):
+ stud_id: str = Field(..., min_length=1)
+ password: str = Field(..., min_length=1)
\ No newline at end of file
diff --git a/ecjtu/ecjtu_api/middle.py b/ecjtu/ecjtu_api/middle.py
new file mode 100644
index 0000000..a178ce8
--- /dev/null
+++ b/ecjtu/ecjtu_api/middle.py
@@ -0,0 +1,32 @@
+from starlette.middleware.base import BaseHTTPMiddleware
+from respose_result import ResponseResult
+from auth import decode
+
+from fastapi import HTTPException
+from fastapi.security import OAuth2PasswordBearer
+from starlette.status import HTTP_401_UNAUTHORIZED
+
+class MyMiddleware(BaseHTTPMiddleware):
+ async def dispatch(self, request, call_next):
+ # 不被拦截的路由
+ secure_routes = ["/token","/login","/","/docs","/openapi.json","/favicon.ico"]
+
+ response = ResponseResult.auth_error()
+ path = request.url.path
+
+ if path not in secure_routes:
+ header = request.headers.get("token")
+ # 是否存在token
+ if not header:
+ return response
+ token = header
+ stud_id, enc_pwd = decode(token)
+ if not stud_id or not enc_pwd:
+ return response
+ response = await call_next(request)
+
+ return response
+
+
+
+
\ No newline at end of file
diff --git a/ecjtu/ecjtu_api/respose_result.py b/ecjtu/ecjtu_api/respose_result.py
new file mode 100644
index 0000000..43cbe47
--- /dev/null
+++ b/ecjtu/ecjtu_api/respose_result.py
@@ -0,0 +1,66 @@
+"""
+构建response返回结果
+{
+ "code": 200,
+ "msg": "success",
+ "data": {
+ "name": "张三",
+ "age": 18
+ }
+}
+"""
+from typing import Any, Dict, Union
+from fastapi.responses import JSONResponse
+
+class ResponseResult:
+ @staticmethod
+ def success(data: Union[Dict[str, Any], Any] = None, msg: str = "success") -> JSONResponse:
+ return JSONResponse(
+ content={
+ "code": 200,
+ "msg": msg,
+ "data": data
+ }
+ )
+ @staticmethod
+ def success_no_data(msg: str = "success") -> JSONResponse:
+ return JSONResponse(
+ content={
+ "code": 200,
+ "msg": msg,
+ "data": None
+ }
+ )
+
+ @staticmethod
+ def not_found(data: Union[Dict[str, Any], Any] = None, msg: str = "not found") -> JSONResponse:
+ return JSONResponse(
+ status_code=404,
+ content={
+ "code": 404,
+ "msg": msg,
+ "data": data
+ }
+ )
+
+ @staticmethod
+ def auth_error(data: Union[Dict[str, Any], Any] = None, msg: str = "auth error") -> JSONResponse:
+ return JSONResponse(
+ status_code=401,
+ content={
+ "code": 401,
+ "msg": msg,
+ "data": data
+ }
+ )
+
+ @staticmethod
+ def error(data: Union[Dict[str, Any], Any] = None, msg: str = "error") -> JSONResponse:
+ return JSONResponse(
+ status_code=500,
+ content={
+ "code": 500,
+ "msg": msg,
+ "data": data
+ }
+ )
\ No newline at end of file
diff --git a/ecjtu/ecjtu_api/server.py b/ecjtu/ecjtu_api/server.py
new file mode 100644
index 0000000..8cf4b0b
--- /dev/null
+++ b/ecjtu/ecjtu_api/server.py
@@ -0,0 +1,18 @@
+#TODO
+
+import argparse
+from ecjtu.client import ECJTU
+
+def main():
+ parser = argparse.ArgumentParser(description="ECJTU Command Line Interface")
+ parser.add_argument('--stud_id', required=True, help='Student ID')
+ parser.add_argument('--pwd', required=True, help='Password')
+ parser.add_argument('--port', type=int, default=8000, help='Port to run the server on')
+
+ args = parser.parse_args()
+
+ client = ECJTU(stud_id=args.stud_id, password=args.pwd)
+ client.start_api_server(port=args.port)
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/ecjtu/server.py b/ecjtu/server.py
deleted file mode 100644
index e69de29..0000000
From 8a2ccca136c7573670bf72ca85563fd9ad69152a Mon Sep 17 00:00:00 2001
From: longsihua2003 <1161443436@qq.com>
Date: Mon, 6 May 2024 22:34:22 +0800
Subject: [PATCH 2/6] add server end
---
ecjtu/ecjtu_api/api.py | 139 ++++++++++++--
ecjtu/ecjtu_api/auth.py | 1 -
ecjtu/ecjtu_api/middle.py | 12 +-
ecjtu/ecjtu_api/respose_result.py | 11 --
ecjtu/ecjtu_api/server.py | 18 --
ecjtu/server.py | 13 ++
examples/ecjtu-api.md | 309 ++++++++++++++++++++++++++++++
examples/ecjtu.api.ipynb | 114 +++++++++++
8 files changed, 559 insertions(+), 58 deletions(-)
delete mode 100644 ecjtu/ecjtu_api/server.py
create mode 100644 ecjtu/server.py
create mode 100644 examples/ecjtu-api.md
create mode 100644 examples/ecjtu.api.ipynb
diff --git a/ecjtu/ecjtu_api/api.py b/ecjtu/ecjtu_api/api.py
index 30b7113..399c908 100644
--- a/ecjtu/ecjtu_api/api.py
+++ b/ecjtu/ecjtu_api/api.py
@@ -1,10 +1,7 @@
-from fastapi import FastAPI, Depends,Header
-from fastapi.security import OAuth2PasswordRequestForm
+from fastapi import FastAPI,Header
+from fastapi.responses import RedirectResponse
-import middle
-from ecjtu_schema import UserSchema, UserLoginSchema
-from respose_result import ResponseResult
-from auth import encode,decode
+from . import middle,ecjtu_schema,respose_result,auth
from ecjtu.client import ECJTU
@@ -12,31 +9,133 @@
app.add_middleware(middle.MyMiddleware)
-@app.post("/login")
-def login(user: UserLoginSchema):
+@app.get("/",include_in_schema=False)
+def push_docs():
+ respose = RedirectResponse(url="/docs")
+ return respose
+
+@app.post("/login",tags=["登录"],summary="登录",description="登录并获取token,以下所有接口都需要token才可以使用")
+def login(user: ecjtu_schema.UserLoginSchema):
client = ECJTU(user.stud_id, user.password)
try:
client.login()
except Exception as e:
- return ResponseResult.error(str(e))
- token = encode(user.stud_id, user.password)
- return ResponseResult.success({"token": token})
+ return respose_result.ResponseResult.error(str(e))
+ token = auth.encode(user.stud_id, user.password)
+ return respose_result.ResponseResult.success({"token": token})
# gpa接口
-@app.get("/gpa")
+@app.get("/gpa",tags=["GPA"],summary="获取GPA",description="获取当学期GPA")
def gpa(token: str = Header(None)):
- stud_id, password = decode(token)
+ stud_id, password = auth.decode(token)
client = ECJTU(stud_id, password)
try:
gpa = client.gpa.today()
except Exception as e:
- return ResponseResult.error(str(e))
- return ResponseResult.success(dict(gpa))
+ return respose_result.ResponseResult.error(str(e))
+ return respose_result.ResponseResult.success(dict(gpa))
+
+# 课表接口
+@app.get("/schedule",tags=["课表"],summary="获取当天课表",description="获取当天课表")
+def schedule(token: str = Header(None)):
+ stud_id, password = auth.decode(token)
+ client = ECJTU(stud_id, password)
+ try:
+ schedule = client.scheduled_courses.today()
+ except Exception as e:
+ return respose_result.ResponseResult.error(str(e))
+ schedule_list = []
+ for sublist in schedule:
+ schedule_list.append(dict(sublist))
+ return respose_result.ResponseResult.success(schedule_list)
+
+@app.get("/schedule/{date}",tags=["课表"],summary="获取指定日期课表",description="获取指定日期课表,指定日期格式为yyyy-mm-dd")
+def schedule_date(token: str = Header(None),date:str = None):
+ # date(str): The date to filter, eg: 2023-01-01
+ stud_id, password = auth.decode(token)
+ client = ECJTU(stud_id, password)
+ try:
+ scheduled_courses = client.scheduled_courses.filter(date=date)
+ except Exception as e:
+ return respose_result.ResponseResult.error(str(e))
+ schedule_list = []
+ for sublist in scheduled_courses:
+ schedule_list.append(dict(sublist))
+ return respose_result.ResponseResult.success(schedule_list)
+
+@app.get("/schedule/week",tags=["课表"],summary="获取本周课表",description="获取本周课表")
+def schedule_week(token: str = Header(None)):
+ stud_id, password = auth.decode(token)
+ client = ECJTU(stud_id, password)
+ try:
+ schedule = client.scheduled_courses.this_week()
+ except Exception as e:
+ return respose_result.ResponseResult.error(str(e))
+ dict_list = []
+ for sublist in schedule:
+ dict_sublist = []
+ for item in sublist:
+ dict_sublist.append(dict(item))
+ dict_list.append(dict_sublist)
+ return respose_result.ResponseResult.success(dict_list)
+
+# 成绩接口
+@app.get("/score",tags=["成绩"],summary="获取当前成绩",description="获取当学期成绩")
+def score(token: str = Header(None)):
+ stud_id, password = auth.decode(token)
+ client = ECJTU(stud_id, password)
+ try:
+ score = client.scores.today()
+ except Exception as e:
+ return respose_result.ResponseResult.error(str(e))
+ score_list = []
+ for sublist in score:
+ score_list.append(dict(sublist))
+ return respose_result.ResponseResult.success(score_list)
+
+@app.get("/score/{semester}",tags=["成绩"],summary="获取指定学期成绩",description="获取指定学期成绩,semester格式为yyyy.1或yyyy.2")
+def score_semester(token: str = Header(None),semester:str = None):
+ # semester(Optional[str]): The semester to filter, eg: 2023.1, 2023.2
+ stud_id, password = auth.decode(token)
+ client = ECJTU(stud_id, password)
+ try:
+ scores = client.scores.filter(semester=semester)
+ except Exception as e:
+ return respose_result.ResponseResult.error(str(e))
+ score_list = []
+ for sublist in scores:
+ score_list.append(dict(sublist))
+ return respose_result.ResponseResult.success(score_list)
+
+# 选课情况接口
+@app.get("/elective_courses",tags=["选课情况"],summary="获取当前选课情况",description="获取当前学期选课情况")
+def elective_courses(token: str = Header(None)):
+ stud_id, password = auth.decode(token)
+ client = ECJTU(stud_id, password)
+ try:
+ elective_courses = client.elective_courses.today()
+ except Exception as e:
+ return respose_result.ResponseResult.error(str(e))
+ elective_courses_list = []
+ for sublist in elective_courses:
+ elective_courses_list.append(dict(sublist))
+ return respose_result.ResponseResult.success(elective_courses_list)
+
+@app.get("/elective_courses/{semester}",tags=["选课情况"],summary="获取指定学期选课情况",description="获取指定学期选课情况,semester格式为yyyy.1或yyyy.2")
+def elective_courses_semester(token: str = Header(None),semester:str = None):
+ # semester(Optional[str]): The semester to filter, eg: 2023.1, 2023.2
+ stud_id, password = auth.decode(token)
+ client = ECJTU(stud_id, password)
+ try:
+ elective_courses = client.elective_courses.filter(semester=semester)
+ except Exception as e:
+ return respose_result.ResponseResult.error(str(e))
+ elective_courses_list = []
+ for sublist in elective_courses:
+ elective_courses_list.append(dict(sublist))
+ return respose_result.ResponseResult.success(elective_courses_list)
# 启动api服务
-def start_api_server():
+def start_api_server(port=8080):
import uvicorn
- uvicorn.run(app, host="127.0.0.1", port=8080)
-
-if __name__ == "__main__":
- start_api_server()
\ No newline at end of file
+ uvicorn.run(app, host="127.0.0.1", port=port)
\ No newline at end of file
diff --git a/ecjtu/ecjtu_api/auth.py b/ecjtu/ecjtu_api/auth.py
index 2c065df..cf86bbf 100644
--- a/ecjtu/ecjtu_api/auth.py
+++ b/ecjtu/ecjtu_api/auth.py
@@ -1,5 +1,4 @@
import base64
-# from ecjtu.client import _get_enc_password
KEY = "zxcvbnmasdgfhjklpoiuytrewq"
def encode(stud_id, pwd):
diff --git a/ecjtu/ecjtu_api/middle.py b/ecjtu/ecjtu_api/middle.py
index a178ce8..e56a722 100644
--- a/ecjtu/ecjtu_api/middle.py
+++ b/ecjtu/ecjtu_api/middle.py
@@ -1,17 +1,13 @@
from starlette.middleware.base import BaseHTTPMiddleware
-from respose_result import ResponseResult
-from auth import decode
+from . import respose_result,auth
-from fastapi import HTTPException
-from fastapi.security import OAuth2PasswordBearer
-from starlette.status import HTTP_401_UNAUTHORIZED
class MyMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
# 不被拦截的路由
- secure_routes = ["/token","/login","/","/docs","/openapi.json","/favicon.ico"]
+ secure_routes = ["/token","/login","/","/docs","/docs/","/openapi.json","/favicon.ico"]
- response = ResponseResult.auth_error()
+ response =respose_result.ResponseResult.auth_error()
path = request.url.path
if path not in secure_routes:
@@ -20,7 +16,7 @@ async def dispatch(self, request, call_next):
if not header:
return response
token = header
- stud_id, enc_pwd = decode(token)
+ stud_id, enc_pwd = auth.decode(token)
if not stud_id or not enc_pwd:
return response
response = await call_next(request)
diff --git a/ecjtu/ecjtu_api/respose_result.py b/ecjtu/ecjtu_api/respose_result.py
index 43cbe47..3f9ccc7 100644
--- a/ecjtu/ecjtu_api/respose_result.py
+++ b/ecjtu/ecjtu_api/respose_result.py
@@ -1,14 +1,3 @@
-"""
-构建response返回结果
-{
- "code": 200,
- "msg": "success",
- "data": {
- "name": "张三",
- "age": 18
- }
-}
-"""
from typing import Any, Dict, Union
from fastapi.responses import JSONResponse
diff --git a/ecjtu/ecjtu_api/server.py b/ecjtu/ecjtu_api/server.py
deleted file mode 100644
index 8cf4b0b..0000000
--- a/ecjtu/ecjtu_api/server.py
+++ /dev/null
@@ -1,18 +0,0 @@
-#TODO
-
-import argparse
-from ecjtu.client import ECJTU
-
-def main():
- parser = argparse.ArgumentParser(description="ECJTU Command Line Interface")
- parser.add_argument('--stud_id', required=True, help='Student ID')
- parser.add_argument('--pwd', required=True, help='Password')
- parser.add_argument('--port', type=int, default=8000, help='Port to run the server on')
-
- args = parser.parse_args()
-
- client = ECJTU(stud_id=args.stud_id, password=args.pwd)
- client.start_api_server(port=args.port)
-
-if __name__ == "__main__":
- main()
\ No newline at end of file
diff --git a/ecjtu/server.py b/ecjtu/server.py
new file mode 100644
index 0000000..3f4c2a9
--- /dev/null
+++ b/ecjtu/server.py
@@ -0,0 +1,13 @@
+import argparse
+from ecjtu_api.api import start_api_server
+
+def main():
+ parser = argparse.ArgumentParser(description="ECJTU Command Line Interface")
+ parser.add_argument('--port', type=int, default=8000, help='Port to run the server on')
+
+ args = parser.parse_args()
+
+ start_api_server(args.port)
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/examples/ecjtu-api.md b/examples/ecjtu-api.md
new file mode 100644
index 0000000..046703c
--- /dev/null
+++ b/examples/ecjtu-api.md
@@ -0,0 +1,309 @@
+---
+title: ecjtu-api
+language_tabs:
+ - shell: Shell
+ - http: HTTP
+ - javascript: JavaScript
+ - ruby: Ruby
+ - python: Python
+ - php: PHP
+ - java: Java
+ - go: Go
+toc_footers: []
+includes: []
+search: true
+code_clipboard: true
+highlight_theme: darkula
+headingLevel: 2
+generator: "@tarslib/widdershins v4.0.23"
+
+---
+
+# ecjtu-api
+
+Base URLs:
+
+# Authentication
+
+# 登录
+
+
+
+## POST 登录
+
+POST /login
+
+登录并获取token,以下所有接口都需要token才可以使用
+
+> Body 请求参数
+
+```json
+{
+ "stud_id": "string",
+ "password": "string"
+}
+```
+
+### 请求参数
+
+|名称|位置|类型|必选|中文名|说明|
+|---|---|---|---|---|---|
+|body|body|[UserLoginSchema](#schemauserloginschema)| 否 | UserLoginSchema|none|
+
+> 返回示例
+
+> 200 Response
+
+```json
+"string"
+```
+
+### 返回结果
+
+|状态码|状态码含义|说明|数据模型|
+|---|---|---|---|
+|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Successful Response|string|
+|422|[Unprocessable Entity](https://tools.ietf.org/html/rfc2518#section-10.3)|Validation Error|[HTTPValidationError](#schemahttpvalidationerror)|
+
+# GPA
+
+
+
+## GET 获取GPA
+
+GET /gpa
+
+获取当学期GPA
+
+### 请求参数
+
+|名称|位置|类型|必选|中文名|说明|
+|---|---|---|---|---|---|
+|token|header|string| 否 | Token|none|
+
+> 返回示例
+
+> 200 Response
+
+```json
+"string"
+```
+
+### 返回结果
+
+|状态码|状态码含义|说明|数据模型|
+|---|---|---|---|
+|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Successful Response|string|
+|422|[Unprocessable Entity](https://tools.ietf.org/html/rfc2518#section-10.3)|Validation Error|[HTTPValidationError](#schemahttpvalidationerror)|
+
+# 课表
+
+
+
+## GET 获取当天课表
+
+GET /schedule
+
+获取当天课表
+
+### 请求参数
+
+|名称|位置|类型|必选|中文名|说明|
+|---|---|---|---|---|---|
+|token|header|string| 否 | Token|none|
+
+> 返回示例
+
+> 200 Response
+
+```json
+"string"
+```
+
+### 返回结果
+
+|状态码|状态码含义|说明|数据模型|
+|---|---|---|---|
+|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Successful Response|string|
+|422|[Unprocessable Entity](https://tools.ietf.org/html/rfc2518#section-10.3)|Validation Error|[HTTPValidationError](#schemahttpvalidationerror)|
+
+
+
+## GET 获取指定日期课表
+
+GET /schedule/{date}
+
+获取指定日期课表,指定日期格式为yyyy-mm-dd
+
+### 请求参数
+
+|名称|位置|类型|必选|中文名|说明|
+|---|---|---|---|---|---|
+|date|path|string| 是 | Date|none|
+|token|header|string| 否 | Token|none|
+
+> 返回示例
+
+> 200 Response
+
+```json
+"string"
+```
+
+### 返回结果
+
+|状态码|状态码含义|说明|数据模型|
+|---|---|---|---|
+|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Successful Response|string|
+|422|[Unprocessable Entity](https://tools.ietf.org/html/rfc2518#section-10.3)|Validation Error|[HTTPValidationError](#schemahttpvalidationerror)|
+
+
+
+## GET 获取本周课表
+
+GET /schedule/week
+
+获取本周课表
+
+### 请求参数
+
+|名称|位置|类型|必选|中文名|说明|
+|---|---|---|---|---|---|
+|token|header|string| 否 | Token|none|
+
+> 返回示例
+
+> 200 Response
+
+```json
+"string"
+```
+
+### 返回结果
+
+|状态码|状态码含义|说明|数据模型|
+|---|---|---|---|
+|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Successful Response|string|
+|422|[Unprocessable Entity](https://tools.ietf.org/html/rfc2518#section-10.3)|Validation Error|[HTTPValidationError](#schemahttpvalidationerror)|
+
+# 成绩
+
+
+
+## GET 获取当前成绩
+
+GET /score
+
+获取当学期成绩
+
+### 请求参数
+
+|名称|位置|类型|必选|中文名|说明|
+|---|---|---|---|---|---|
+|token|header|string| 否 | Token|none|
+
+> 返回示例
+
+> 200 Response
+
+```json
+"string"
+```
+
+### 返回结果
+
+|状态码|状态码含义|说明|数据模型|
+|---|---|---|---|
+|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Successful Response|string|
+|422|[Unprocessable Entity](https://tools.ietf.org/html/rfc2518#section-10.3)|Validation Error|[HTTPValidationError](#schemahttpvalidationerror)|
+
+
+
+## GET 获取指定学期成绩
+
+GET /score/{semester}
+
+获取指定学期成绩,semester格式为yyyy.1或yyyy.2
+
+### 请求参数
+
+|名称|位置|类型|必选|中文名|说明|
+|---|---|---|---|---|---|
+|semester|path|string| 是 | Semester|none|
+|token|header|string| 否 | Token|none|
+
+> 返回示例
+
+> 200 Response
+
+```json
+"string"
+```
+
+### 返回结果
+
+|状态码|状态码含义|说明|数据模型|
+|---|---|---|---|
+|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Successful Response|string|
+|422|[Unprocessable Entity](https://tools.ietf.org/html/rfc2518#section-10.3)|Validation Error|[HTTPValidationError](#schemahttpvalidationerror)|
+
+# 选课情况
+
+
+
+## GET 获取当前选课情况
+
+GET /elective_courses
+
+获取当前学期选课情况
+
+### 请求参数
+
+|名称|位置|类型|必选|中文名|说明|
+|---|---|---|---|---|---|
+|token|header|string| 否 | Token|none|
+
+> 返回示例
+
+> 200 Response
+
+```json
+"string"
+```
+
+### 返回结果
+
+|状态码|状态码含义|说明|数据模型|
+|---|---|---|---|
+|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Successful Response|string|
+|422|[Unprocessable Entity](https://tools.ietf.org/html/rfc2518#section-10.3)|Validation Error|[HTTPValidationError](#schemahttpvalidationerror)|
+
+
+
+## GET 获取指定学期选课情况
+
+GET /elective_courses/{semester}
+
+获取指定学期选课情况,semester格式为yyyy.1或yyyy.2
+
+### 请求参数
+
+|名称|位置|类型|必选|中文名|说明|
+|---|---|---|---|---|---|
+|semester|path|string| 是 | Semester|none|
+|token|header|string| 否 | Token|none|
+
+> 返回示例
+
+> 200 Response
+
+```json
+"string"
+```
+
+### 返回结果
+
+|状态码|状态码含义|说明|数据模型|
+|---|---|---|---|
+|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Successful Response|string|
+|422|[Unprocessable Entity](https://tools.ietf.org/html/rfc2518#section-10.3)|Validation Error|[HTTPValidationError](#schemahttpvalidationerror)|
\ No newline at end of file
diff --git a/examples/ecjtu.api.ipynb b/examples/ecjtu.api.ipynb
new file mode 100644
index 0000000..ffd520c
--- /dev/null
+++ b/examples/ecjtu.api.ipynb
@@ -0,0 +1,114 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# ecjtu-api\n",
+ "我们提供了ecjtu的一套服务。\n",
+ "通过fastapi的形式实现"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 相关接口描述\n",
+ "\n",
+ "详细信息可以参考ecjtu-api.md。\n",
+ "\n",
+ "1. 登录\n",
+ " \n",
+ " post /login \n",
+ " 通过学号和密码进行登录,获取登录token,其他接口的请求均要token认证\n",
+ "2. gpa\n",
+ " \n",
+ " get /gpa\n",
+ " 获取当前gpa情况\n",
+ "3. 课表\n",
+ " * get /schedule\n",
+ " 获取今日课表\n",
+ " * get /schedule/{date}\n",
+ " 获取指定日期课表 date格式为2024-05-01\n",
+ " * get /schedule/week\n",
+ " 获取本周课表\n",
+ "4. 成绩\n",
+ " * get /score\n",
+ " 获取目前成绩\n",
+ " * /score/{semester}\n",
+ " 获取指定学期成绩 semester格式为2023.1\n",
+ "5. 选课情况\n",
+ " * get /elective_courses\n",
+ " 获取当前选课信息\n",
+ " * get /elective_courses/{semester}\n",
+ " 获取指定学期选课信息 semester格式为2023.1"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 服务的启动\n",
+ "我们提供了两种服务的启动方式\n",
+ "\n",
+ "### 通过python文件启动"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from ecjtu.ecjtu_api.api import start_api_server\n",
+ "\n",
+ "def main():\n",
+ " start_api_server(port=8080)\n",
+ "\n",
+ "if __name__ == '__main__':\n",
+ " main()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 通过命令行启动"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "vscode": {
+ "languageId": "shellscript"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "python ecjtu/server.py --port 8080 "
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "ecjtut",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.10.14"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
From 4016736b34604c57f12e645ff35f4050a9d4dea0 Mon Sep 17 00:00:00 2001
From: longsihua2003 <1161443436@qq.com>
Date: Mon, 6 May 2024 22:39:00 +0800
Subject: [PATCH 3/6] lint and formatting
---
ecjtu/ecjtu_api/api.py | 75 ++-
ecjtu/ecjtu_api/auth.py | 5 +-
ecjtu/ecjtu_api/ecjtu_schema.py | 3 +-
ecjtu/ecjtu_api/middle.py | 21 +-
ecjtu/ecjtu_api/respose_result.py | 60 +--
ecjtu/server.py | 9 +-
poetry.lock | 851 +++++++++++++++++++++++++++++-
pyproject.toml | 2 +
8 files changed, 956 insertions(+), 70 deletions(-)
diff --git a/ecjtu/ecjtu_api/api.py b/ecjtu/ecjtu_api/api.py
index 399c908..2db1afb 100644
--- a/ecjtu/ecjtu_api/api.py
+++ b/ecjtu/ecjtu_api/api.py
@@ -1,20 +1,27 @@
-from fastapi import FastAPI,Header
+from fastapi import FastAPI, Header
from fastapi.responses import RedirectResponse
-from . import middle,ecjtu_schema,respose_result,auth
-
from ecjtu.client import ECJTU
+from . import auth, ecjtu_schema, middle, respose_result
+
app = FastAPI(title="ECJTU API", description="API for ECJTU")
app.add_middleware(middle.MyMiddleware)
-@app.get("/",include_in_schema=False)
+
+@app.get("/", include_in_schema=False)
def push_docs():
respose = RedirectResponse(url="/docs")
return respose
-@app.post("/login",tags=["登录"],summary="登录",description="登录并获取token,以下所有接口都需要token才可以使用")
+
+@app.post(
+ "/login",
+ tags=["登录"],
+ summary="登录",
+ description="登录并获取token,以下所有接口都需要token才可以使用",
+)
def login(user: ecjtu_schema.UserLoginSchema):
client = ECJTU(user.stud_id, user.password)
try:
@@ -24,8 +31,9 @@ def login(user: ecjtu_schema.UserLoginSchema):
token = auth.encode(user.stud_id, user.password)
return respose_result.ResponseResult.success({"token": token})
+
# gpa接口
-@app.get("/gpa",tags=["GPA"],summary="获取GPA",description="获取当学期GPA")
+@app.get("/gpa", tags=["GPA"], summary="获取GPA", description="获取当学期GPA")
def gpa(token: str = Header(None)):
stud_id, password = auth.decode(token)
client = ECJTU(stud_id, password)
@@ -35,8 +43,9 @@ def gpa(token: str = Header(None)):
return respose_result.ResponseResult.error(str(e))
return respose_result.ResponseResult.success(dict(gpa))
+
# 课表接口
-@app.get("/schedule",tags=["课表"],summary="获取当天课表",description="获取当天课表")
+@app.get("/schedule", tags=["课表"], summary="获取当天课表", description="获取当天课表")
def schedule(token: str = Header(None)):
stud_id, password = auth.decode(token)
client = ECJTU(stud_id, password)
@@ -49,8 +58,14 @@ def schedule(token: str = Header(None)):
schedule_list.append(dict(sublist))
return respose_result.ResponseResult.success(schedule_list)
-@app.get("/schedule/{date}",tags=["课表"],summary="获取指定日期课表",description="获取指定日期课表,指定日期格式为yyyy-mm-dd")
-def schedule_date(token: str = Header(None),date:str = None):
+
+@app.get(
+ "/schedule/{date}",
+ tags=["课表"],
+ summary="获取指定日期课表",
+ description="获取指定日期课表,指定日期格式为yyyy-mm-dd",
+)
+def schedule_date(token: str = Header(None), date: str = None):
# date(str): The date to filter, eg: 2023-01-01
stud_id, password = auth.decode(token)
client = ECJTU(stud_id, password)
@@ -63,14 +78,17 @@ def schedule_date(token: str = Header(None),date:str = None):
schedule_list.append(dict(sublist))
return respose_result.ResponseResult.success(schedule_list)
-@app.get("/schedule/week",tags=["课表"],summary="获取本周课表",description="获取本周课表")
+
+@app.get(
+ "/schedule/week", tags=["课表"], summary="获取本周课表", description="获取本周课表"
+)
def schedule_week(token: str = Header(None)):
stud_id, password = auth.decode(token)
client = ECJTU(stud_id, password)
try:
schedule = client.scheduled_courses.this_week()
except Exception as e:
- return respose_result.ResponseResult.error(str(e))
+ return respose_result.ResponseResult.error(str(e))
dict_list = []
for sublist in schedule:
dict_sublist = []
@@ -79,8 +97,9 @@ def schedule_week(token: str = Header(None)):
dict_list.append(dict_sublist)
return respose_result.ResponseResult.success(dict_list)
+
# 成绩接口
-@app.get("/score",tags=["成绩"],summary="获取当前成绩",description="获取当学期成绩")
+@app.get("/score", tags=["成绩"], summary="获取当前成绩", description="获取当学期成绩")
def score(token: str = Header(None)):
stud_id, password = auth.decode(token)
client = ECJTU(stud_id, password)
@@ -93,8 +112,14 @@ def score(token: str = Header(None)):
score_list.append(dict(sublist))
return respose_result.ResponseResult.success(score_list)
-@app.get("/score/{semester}",tags=["成绩"],summary="获取指定学期成绩",description="获取指定学期成绩,semester格式为yyyy.1或yyyy.2")
-def score_semester(token: str = Header(None),semester:str = None):
+
+@app.get(
+ "/score/{semester}",
+ tags=["成绩"],
+ summary="获取指定学期成绩",
+ description="获取指定学期成绩,semester格式为yyyy.1或yyyy.2",
+)
+def score_semester(token: str = Header(None), semester: str = None):
# semester(Optional[str]): The semester to filter, eg: 2023.1, 2023.2
stud_id, password = auth.decode(token)
client = ECJTU(stud_id, password)
@@ -107,8 +132,14 @@ def score_semester(token: str = Header(None),semester:str = None):
score_list.append(dict(sublist))
return respose_result.ResponseResult.success(score_list)
+
# 选课情况接口
-@app.get("/elective_courses",tags=["选课情况"],summary="获取当前选课情况",description="获取当前学期选课情况")
+@app.get(
+ "/elective_courses",
+ tags=["选课情况"],
+ summary="获取当前选课情况",
+ description="获取当前学期选课情况",
+)
def elective_courses(token: str = Header(None)):
stud_id, password = auth.decode(token)
client = ECJTU(stud_id, password)
@@ -121,8 +152,14 @@ def elective_courses(token: str = Header(None)):
elective_courses_list.append(dict(sublist))
return respose_result.ResponseResult.success(elective_courses_list)
-@app.get("/elective_courses/{semester}",tags=["选课情况"],summary="获取指定学期选课情况",description="获取指定学期选课情况,semester格式为yyyy.1或yyyy.2")
-def elective_courses_semester(token: str = Header(None),semester:str = None):
+
+@app.get(
+ "/elective_courses/{semester}",
+ tags=["选课情况"],
+ summary="获取指定学期选课情况",
+ description="获取指定学期选课情况,semester格式为yyyy.1或yyyy.2",
+)
+def elective_courses_semester(token: str = Header(None), semester: str = None):
# semester(Optional[str]): The semester to filter, eg: 2023.1, 2023.2
stud_id, password = auth.decode(token)
client = ECJTU(stud_id, password)
@@ -135,7 +172,9 @@ def elective_courses_semester(token: str = Header(None),semester:str = None):
elective_courses_list.append(dict(sublist))
return respose_result.ResponseResult.success(elective_courses_list)
+
# 启动api服务
def start_api_server(port=8080):
import uvicorn
- uvicorn.run(app, host="127.0.0.1", port=port)
\ No newline at end of file
+
+ uvicorn.run(app, host="127.0.0.1", port=port)
diff --git a/ecjtu/ecjtu_api/auth.py b/ecjtu/ecjtu_api/auth.py
index cf86bbf..8ba0050 100644
--- a/ecjtu/ecjtu_api/auth.py
+++ b/ecjtu/ecjtu_api/auth.py
@@ -1,11 +1,14 @@
import base64
+
KEY = "zxcvbnmasdgfhjklpoiuytrewq"
+
def encode(stud_id, pwd):
# enc_pwd = _get_enc_password(pwd);
token = base64.b64encode(f"{stud_id}:{pwd}".encode()).decode()
return token
+
def decode(token):
stud_id, enc_pwd = base64.b64decode(token.encode()).decode().split(":")
- return stud_id, enc_pwd
\ No newline at end of file
+ return stud_id, enc_pwd
diff --git a/ecjtu/ecjtu_api/ecjtu_schema.py b/ecjtu/ecjtu_api/ecjtu_schema.py
index 1fb7bd9..8a4406d 100644
--- a/ecjtu/ecjtu_api/ecjtu_schema.py
+++ b/ecjtu/ecjtu_api/ecjtu_schema.py
@@ -1,5 +1,6 @@
from pydantic import BaseModel, Field
+
class UserLoginSchema(BaseModel):
stud_id: str = Field(..., min_length=1)
- password: str = Field(..., min_length=1)
\ No newline at end of file
+ password: str = Field(..., min_length=1)
diff --git a/ecjtu/ecjtu_api/middle.py b/ecjtu/ecjtu_api/middle.py
index e56a722..e69d75a 100644
--- a/ecjtu/ecjtu_api/middle.py
+++ b/ecjtu/ecjtu_api/middle.py
@@ -1,13 +1,22 @@
from starlette.middleware.base import BaseHTTPMiddleware
-from . import respose_result,auth
+
+from . import auth, respose_result
class MyMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
# 不被拦截的路由
- secure_routes = ["/token","/login","/","/docs","/docs/","/openapi.json","/favicon.ico"]
-
- response =respose_result.ResponseResult.auth_error()
+ secure_routes = [
+ "/token",
+ "/login",
+ "/",
+ "/docs",
+ "/docs/",
+ "/openapi.json",
+ "/favicon.ico",
+ ]
+
+ response = respose_result.ResponseResult.auth_error()
path = request.url.path
if path not in secure_routes:
@@ -22,7 +31,3 @@ async def dispatch(self, request, call_next):
response = await call_next(request)
return response
-
-
-
-
\ No newline at end of file
diff --git a/ecjtu/ecjtu_api/respose_result.py b/ecjtu/ecjtu_api/respose_result.py
index 3f9ccc7..a3a05c4 100644
--- a/ecjtu/ecjtu_api/respose_result.py
+++ b/ecjtu/ecjtu_api/respose_result.py
@@ -1,55 +1,39 @@
from typing import Any, Dict, Union
+
from fastapi.responses import JSONResponse
+
class ResponseResult:
@staticmethod
- def success(data: Union[Dict[str, Any], Any] = None, msg: str = "success") -> JSONResponse:
- return JSONResponse(
- content={
- "code": 200,
- "msg": msg,
- "data": data
- }
- )
+ def success(
+ data: Union[Dict[str, Any], Any] = None, msg: str = "success"
+ ) -> JSONResponse:
+ return JSONResponse(content={"code": 200, "msg": msg, "data": data})
+
@staticmethod
def success_no_data(msg: str = "success") -> JSONResponse:
- return JSONResponse(
- content={
- "code": 200,
- "msg": msg,
- "data": None
- }
- )
+ return JSONResponse(content={"code": 200, "msg": msg, "data": None})
@staticmethod
- def not_found(data: Union[Dict[str, Any], Any] = None, msg: str = "not found") -> JSONResponse:
+ def not_found(
+ data: Union[Dict[str, Any], Any] = None, msg: str = "not found"
+ ) -> JSONResponse:
return JSONResponse(
- status_code=404,
- content={
- "code": 404,
- "msg": msg,
- "data": data
- }
+ status_code=404, content={"code": 404, "msg": msg, "data": data}
)
-
+
@staticmethod
- def auth_error(data: Union[Dict[str, Any], Any] = None, msg: str = "auth error") -> JSONResponse:
+ def auth_error(
+ data: Union[Dict[str, Any], Any] = None, msg: str = "auth error"
+ ) -> JSONResponse:
return JSONResponse(
- status_code=401,
- content={
- "code": 401,
- "msg": msg,
- "data": data
- }
+ status_code=401, content={"code": 401, "msg": msg, "data": data}
)
@staticmethod
- def error(data: Union[Dict[str, Any], Any] = None, msg: str = "error") -> JSONResponse:
+ def error(
+ data: Union[Dict[str, Any], Any] = None, msg: str = "error"
+ ) -> JSONResponse:
return JSONResponse(
- status_code=500,
- content={
- "code": 500,
- "msg": msg,
- "data": data
- }
- )
\ No newline at end of file
+ status_code=500, content={"code": 500, "msg": msg, "data": data}
+ )
diff --git a/ecjtu/server.py b/ecjtu/server.py
index 3f4c2a9..8317631 100644
--- a/ecjtu/server.py
+++ b/ecjtu/server.py
@@ -1,13 +1,18 @@
import argparse
+
from ecjtu_api.api import start_api_server
+
def main():
parser = argparse.ArgumentParser(description="ECJTU Command Line Interface")
- parser.add_argument('--port', type=int, default=8000, help='Port to run the server on')
+ parser.add_argument(
+ "--port", type=int, default=8000, help="Port to run the server on"
+ )
args = parser.parse_args()
start_api_server(args.port)
+
if __name__ == "__main__":
- main()
\ No newline at end of file
+ main()
diff --git a/poetry.lock b/poetry.lock
index 1be763a..4528467 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,4 +1,4 @@
-# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand.
+# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
[[package]]
name = "annotated-types"
@@ -14,6 +14,28 @@ files = [
[package.dependencies]
typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""}
+[[package]]
+name = "anyio"
+version = "4.3.0"
+description = "High level compatibility layer for multiple asynchronous event loop implementations"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8"},
+ {file = "anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6"},
+]
+
+[package.dependencies]
+exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""}
+idna = ">=2.8"
+sniffio = ">=1.1"
+typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""}
+
+[package.extras]
+doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
+test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
+trio = ["trio (>=0.23)"]
+
[[package]]
name = "beautifulsoup4"
version = "4.12.3"
@@ -35,6 +57,17 @@ charset-normalizer = ["charset-normalizer"]
html5lib = ["html5lib"]
lxml = ["lxml"]
+[[package]]
+name = "certifi"
+version = "2024.2.2"
+description = "Python package for providing Mozilla's CA Bundle."
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"},
+ {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"},
+]
+
[[package]]
name = "cffi"
version = "1.16.0"
@@ -110,6 +143,20 @@ files = [
{file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"},
]
+[[package]]
+name = "click"
+version = "8.1.7"
+description = "Composable command line interface toolkit"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
+ {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
+]
+
+[package.dependencies]
+colorama = {version = "*", markers = "platform_system == \"Windows\""}
+
[[package]]
name = "colorama"
version = "0.4.6"
@@ -265,6 +312,41 @@ files = [
{file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"},
]
+[[package]]
+name = "dnspython"
+version = "2.6.1"
+description = "DNS toolkit"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"},
+ {file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"},
+]
+
+[package.extras]
+dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "sphinx (>=7.2.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"]
+dnssec = ["cryptography (>=41)"]
+doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"]
+doq = ["aioquic (>=0.9.25)"]
+idna = ["idna (>=3.6)"]
+trio = ["trio (>=0.23)"]
+wmi = ["wmi (>=1.5.1)"]
+
+[[package]]
+name = "email-validator"
+version = "2.1.1"
+description = "A robust email address syntax and deliverability validation library."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "email_validator-2.1.1-py3-none-any.whl", hash = "sha256:97d882d174e2a65732fb43bfce81a3a834cbc1bde8bf419e30ef5ea976370a05"},
+ {file = "email_validator-2.1.1.tar.gz", hash = "sha256:200a70680ba08904be6d1eef729205cc0d687634399a5924d842533efb824b84"},
+]
+
+[package.dependencies]
+dnspython = ">=2.0.0"
+idna = ">=2.0.0"
+
[[package]]
name = "exceptiongroup"
version = "1.2.1"
@@ -279,6 +361,49 @@ files = [
[package.extras]
test = ["pytest (>=6)"]
+[[package]]
+name = "fastapi"
+version = "0.111.0"
+description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "fastapi-0.111.0-py3-none-any.whl", hash = "sha256:97ecbf994be0bcbdadedf88c3150252bed7b2087075ac99735403b1b76cc8fc0"},
+ {file = "fastapi-0.111.0.tar.gz", hash = "sha256:b9db9dd147c91cb8b769f7183535773d8741dd46f9dc6676cd82eab510228cd7"},
+]
+
+[package.dependencies]
+email_validator = ">=2.0.0"
+fastapi-cli = ">=0.0.2"
+httpx = ">=0.23.0"
+jinja2 = ">=2.11.2"
+orjson = ">=3.2.1"
+pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0"
+python-multipart = ">=0.0.7"
+starlette = ">=0.37.2,<0.38.0"
+typing-extensions = ">=4.8.0"
+ujson = ">=4.0.1,<4.0.2 || >4.0.2,<4.1.0 || >4.1.0,<4.2.0 || >4.2.0,<4.3.0 || >4.3.0,<5.0.0 || >5.0.0,<5.1.0 || >5.1.0"
+uvicorn = {version = ">=0.12.0", extras = ["standard"]}
+
+[package.extras]
+all = ["email_validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"]
+
+[[package]]
+name = "fastapi-cli"
+version = "0.0.2"
+description = "Run and manage FastAPI apps from the command line with FastAPI CLI. 🚀"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "fastapi_cli-0.0.2-py3-none-any.whl", hash = "sha256:d7a8ec89fd52ad16c52de9fe7299e4b22c7a32e1bf28aa886e5517e4927423dd"},
+ {file = "fastapi_cli-0.0.2.tar.gz", hash = "sha256:589565ba758432632eadcf7b950e0ec76bb283b549784d9df17f261a8a9de476"},
+]
+
+[package.dependencies]
+fastapi = "*"
+typer = ">=0.12.3"
+uvicorn = {version = ">=0.29.0", extras = ["standard"]}
+
[[package]]
name = "filelock"
version = "3.13.4"
@@ -295,6 +420,110 @@ docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1
testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"]
typing = ["typing-extensions (>=4.8)"]
+[[package]]
+name = "h11"
+version = "0.14.0"
+description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
+ {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
+]
+
+[[package]]
+name = "httpcore"
+version = "1.0.5"
+description = "A minimal low-level HTTP client."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"},
+ {file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"},
+]
+
+[package.dependencies]
+certifi = "*"
+h11 = ">=0.13,<0.15"
+
+[package.extras]
+asyncio = ["anyio (>=4.0,<5.0)"]
+http2 = ["h2 (>=3,<5)"]
+socks = ["socksio (==1.*)"]
+trio = ["trio (>=0.22.0,<0.26.0)"]
+
+[[package]]
+name = "httptools"
+version = "0.6.1"
+description = "A collection of framework independent HTTP protocol utils."
+optional = false
+python-versions = ">=3.8.0"
+files = [
+ {file = "httptools-0.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d2f6c3c4cb1948d912538217838f6e9960bc4a521d7f9b323b3da579cd14532f"},
+ {file = "httptools-0.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:00d5d4b68a717765b1fabfd9ca755bd12bf44105eeb806c03d1962acd9b8e563"},
+ {file = "httptools-0.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:639dc4f381a870c9ec860ce5c45921db50205a37cc3334e756269736ff0aac58"},
+ {file = "httptools-0.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e57997ac7fb7ee43140cc03664de5f268813a481dff6245e0075925adc6aa185"},
+ {file = "httptools-0.6.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0ac5a0ae3d9f4fe004318d64b8a854edd85ab76cffbf7ef5e32920faef62f142"},
+ {file = "httptools-0.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3f30d3ce413088a98b9db71c60a6ada2001a08945cb42dd65a9a9fe228627658"},
+ {file = "httptools-0.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:1ed99a373e327f0107cb513b61820102ee4f3675656a37a50083eda05dc9541b"},
+ {file = "httptools-0.6.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7a7ea483c1a4485c71cb5f38be9db078f8b0e8b4c4dc0210f531cdd2ddac1ef1"},
+ {file = "httptools-0.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:85ed077c995e942b6f1b07583e4eb0a8d324d418954fc6af913d36db7c05a5a0"},
+ {file = "httptools-0.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b0bb634338334385351a1600a73e558ce619af390c2b38386206ac6a27fecfc"},
+ {file = "httptools-0.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d9ceb2c957320def533671fc9c715a80c47025139c8d1f3797477decbc6edd2"},
+ {file = "httptools-0.6.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4f0f8271c0a4db459f9dc807acd0eadd4839934a4b9b892f6f160e94da309837"},
+ {file = "httptools-0.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6a4f5ccead6d18ec072ac0b84420e95d27c1cdf5c9f1bc8fbd8daf86bd94f43d"},
+ {file = "httptools-0.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:5cceac09f164bcba55c0500a18fe3c47df29b62353198e4f37bbcc5d591172c3"},
+ {file = "httptools-0.6.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:75c8022dca7935cba14741a42744eee13ba05db00b27a4b940f0d646bd4d56d0"},
+ {file = "httptools-0.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:48ed8129cd9a0d62cf4d1575fcf90fb37e3ff7d5654d3a5814eb3d55f36478c2"},
+ {file = "httptools-0.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f58e335a1402fb5a650e271e8c2d03cfa7cea46ae124649346d17bd30d59c90"},
+ {file = "httptools-0.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93ad80d7176aa5788902f207a4e79885f0576134695dfb0fefc15b7a4648d503"},
+ {file = "httptools-0.6.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9bb68d3a085c2174c2477eb3ffe84ae9fb4fde8792edb7bcd09a1d8467e30a84"},
+ {file = "httptools-0.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b512aa728bc02354e5ac086ce76c3ce635b62f5fbc32ab7082b5e582d27867bb"},
+ {file = "httptools-0.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:97662ce7fb196c785344d00d638fc9ad69e18ee4bfb4000b35a52efe5adcc949"},
+ {file = "httptools-0.6.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8e216a038d2d52ea13fdd9b9c9c7459fb80d78302b257828285eca1c773b99b3"},
+ {file = "httptools-0.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3e802e0b2378ade99cd666b5bffb8b2a7cc8f3d28988685dc300469ea8dd86cb"},
+ {file = "httptools-0.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4bd3e488b447046e386a30f07af05f9b38d3d368d1f7b4d8f7e10af85393db97"},
+ {file = "httptools-0.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe467eb086d80217b7584e61313ebadc8d187a4d95bb62031b7bab4b205c3ba3"},
+ {file = "httptools-0.6.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3c3b214ce057c54675b00108ac42bacf2ab8f85c58e3f324a4e963bbc46424f4"},
+ {file = "httptools-0.6.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8ae5b97f690badd2ca27cbf668494ee1b6d34cf1c464271ef7bfa9ca6b83ffaf"},
+ {file = "httptools-0.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:405784577ba6540fa7d6ff49e37daf104e04f4b4ff2d1ac0469eaa6a20fde084"},
+ {file = "httptools-0.6.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:95fb92dd3649f9cb139e9c56604cc2d7c7bf0fc2e7c8d7fbd58f96e35eddd2a3"},
+ {file = "httptools-0.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dcbab042cc3ef272adc11220517278519adf8f53fd3056d0e68f0a6f891ba94e"},
+ {file = "httptools-0.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cf2372e98406efb42e93bfe10f2948e467edfd792b015f1b4ecd897903d3e8d"},
+ {file = "httptools-0.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:678fcbae74477a17d103b7cae78b74800d795d702083867ce160fc202104d0da"},
+ {file = "httptools-0.6.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e0b281cf5a125c35f7f6722b65d8542d2e57331be573e9e88bc8b0115c4a7a81"},
+ {file = "httptools-0.6.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:95658c342529bba4e1d3d2b1a874db16c7cca435e8827422154c9da76ac4e13a"},
+ {file = "httptools-0.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:7ebaec1bf683e4bf5e9fbb49b8cc36da482033596a415b3e4ebab5a4c0d7ec5e"},
+ {file = "httptools-0.6.1.tar.gz", hash = "sha256:c6e26c30455600b95d94b1b836085138e82f177351454ee841c148f93a9bad5a"},
+]
+
+[package.extras]
+test = ["Cython (>=0.29.24,<0.30.0)"]
+
+[[package]]
+name = "httpx"
+version = "0.27.0"
+description = "The next generation HTTP client."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"},
+ {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"},
+]
+
+[package.dependencies]
+anyio = "*"
+certifi = "*"
+httpcore = "==1.*"
+idna = "*"
+sniffio = "*"
+
+[package.extras]
+brotli = ["brotli", "brotlicffi"]
+cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"]
+http2 = ["h2 (>=3,<5)"]
+socks = ["socksio (==1.*)"]
+
[[package]]
name = "identify"
version = "2.5.35"
@@ -309,6 +538,17 @@ files = [
[package.extras]
license = ["ukkonen"]
+[[package]]
+name = "idna"
+version = "3.7"
+description = "Internationalized Domain Names in Applications (IDNA)"
+optional = false
+python-versions = ">=3.5"
+files = [
+ {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"},
+ {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"},
+]
+
[[package]]
name = "iniconfig"
version = "2.0.0"
@@ -320,6 +560,127 @@ files = [
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
]
+[[package]]
+name = "jinja2"
+version = "3.1.4"
+description = "A very fast and expressive template engine."
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"},
+ {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"},
+]
+
+[package.dependencies]
+MarkupSafe = ">=2.0"
+
+[package.extras]
+i18n = ["Babel (>=2.7)"]
+
+[[package]]
+name = "markdown-it-py"
+version = "3.0.0"
+description = "Python port of markdown-it. Markdown parsing, done right!"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"},
+ {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"},
+]
+
+[package.dependencies]
+mdurl = ">=0.1,<1.0"
+
+[package.extras]
+benchmarking = ["psutil", "pytest", "pytest-benchmark"]
+code-style = ["pre-commit (>=3.0,<4.0)"]
+compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"]
+linkify = ["linkify-it-py (>=1,<3)"]
+plugins = ["mdit-py-plugins"]
+profiling = ["gprof2dot"]
+rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"]
+testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"]
+
+[[package]]
+name = "markupsafe"
+version = "2.1.5"
+description = "Safely add untrusted strings to HTML/XML markup."
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"},
+ {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"},
+ {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"},
+ {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"},
+ {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"},
+ {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"},
+ {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"},
+ {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"},
+ {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"},
+ {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"},
+ {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"},
+ {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"},
+ {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"},
+ {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"},
+ {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"},
+ {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"},
+ {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"},
+ {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"},
+ {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"},
+ {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"},
+ {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"},
+ {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"},
+ {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"},
+ {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"},
+ {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"},
+ {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"},
+ {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"},
+ {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"},
+ {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"},
+ {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"},
+ {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"},
+ {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"},
+ {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"},
+ {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"},
+ {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"},
+ {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"},
+ {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"},
+ {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"},
+ {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"},
+ {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"},
+ {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"},
+ {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"},
+ {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"},
+ {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"},
+ {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"},
+ {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"},
+ {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"},
+ {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"},
+ {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"},
+ {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"},
+ {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"},
+ {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"},
+ {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"},
+ {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"},
+ {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"},
+ {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"},
+ {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"},
+ {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"},
+ {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"},
+ {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"},
+]
+
+[[package]]
+name = "mdurl"
+version = "0.1.2"
+description = "Markdown URL utilities"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"},
+ {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
+]
+
[[package]]
name = "nodeenv"
version = "1.8.0"
@@ -334,6 +695,61 @@ files = [
[package.dependencies]
setuptools = "*"
+[[package]]
+name = "orjson"
+version = "3.10.3"
+description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "orjson-3.10.3-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9fb6c3f9f5490a3eb4ddd46fc1b6eadb0d6fc16fb3f07320149c3286a1409dd8"},
+ {file = "orjson-3.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:252124b198662eee80428f1af8c63f7ff077c88723fe206a25df8dc57a57b1fa"},
+ {file = "orjson-3.10.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9f3e87733823089a338ef9bbf363ef4de45e5c599a9bf50a7a9b82e86d0228da"},
+ {file = "orjson-3.10.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8334c0d87103bb9fbbe59b78129f1f40d1d1e8355bbed2ca71853af15fa4ed3"},
+ {file = "orjson-3.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1952c03439e4dce23482ac846e7961f9d4ec62086eb98ae76d97bd41d72644d7"},
+ {file = "orjson-3.10.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c0403ed9c706dcd2809f1600ed18f4aae50be263bd7112e54b50e2c2bc3ebd6d"},
+ {file = "orjson-3.10.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:382e52aa4270a037d41f325e7d1dfa395b7de0c367800b6f337d8157367bf3a7"},
+ {file = "orjson-3.10.3-cp310-none-win32.whl", hash = "sha256:be2aab54313752c04f2cbaab4515291ef5af8c2256ce22abc007f89f42f49109"},
+ {file = "orjson-3.10.3-cp310-none-win_amd64.whl", hash = "sha256:416b195f78ae461601893f482287cee1e3059ec49b4f99479aedf22a20b1098b"},
+ {file = "orjson-3.10.3-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:73100d9abbbe730331f2242c1fc0bcb46a3ea3b4ae3348847e5a141265479700"},
+ {file = "orjson-3.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:544a12eee96e3ab828dbfcb4d5a0023aa971b27143a1d35dc214c176fdfb29b3"},
+ {file = "orjson-3.10.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:520de5e2ef0b4ae546bea25129d6c7c74edb43fc6cf5213f511a927f2b28148b"},
+ {file = "orjson-3.10.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ccaa0a401fc02e8828a5bedfd80f8cd389d24f65e5ca3954d72c6582495b4bcf"},
+ {file = "orjson-3.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a7bc9e8bc11bac40f905640acd41cbeaa87209e7e1f57ade386da658092dc16"},
+ {file = "orjson-3.10.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3582b34b70543a1ed6944aca75e219e1192661a63da4d039d088a09c67543b08"},
+ {file = "orjson-3.10.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1c23dfa91481de880890d17aa7b91d586a4746a4c2aa9a145bebdbaf233768d5"},
+ {file = "orjson-3.10.3-cp311-none-win32.whl", hash = "sha256:1770e2a0eae728b050705206d84eda8b074b65ee835e7f85c919f5705b006c9b"},
+ {file = "orjson-3.10.3-cp311-none-win_amd64.whl", hash = "sha256:93433b3c1f852660eb5abdc1f4dd0ced2be031ba30900433223b28ee0140cde5"},
+ {file = "orjson-3.10.3-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a39aa73e53bec8d410875683bfa3a8edf61e5a1c7bb4014f65f81d36467ea098"},
+ {file = "orjson-3.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0943a96b3fa09bee1afdfccc2cb236c9c64715afa375b2af296c73d91c23eab2"},
+ {file = "orjson-3.10.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e852baafceff8da3c9defae29414cc8513a1586ad93e45f27b89a639c68e8176"},
+ {file = "orjson-3.10.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18566beb5acd76f3769c1d1a7ec06cdb81edc4d55d2765fb677e3eaa10fa99e0"},
+ {file = "orjson-3.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bd2218d5a3aa43060efe649ec564ebedec8ce6ae0a43654b81376216d5ebd42"},
+ {file = "orjson-3.10.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cf20465e74c6e17a104ecf01bf8cd3b7b252565b4ccee4548f18b012ff2f8069"},
+ {file = "orjson-3.10.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ba7f67aa7f983c4345eeda16054a4677289011a478ca947cd69c0a86ea45e534"},
+ {file = "orjson-3.10.3-cp312-none-win32.whl", hash = "sha256:17e0713fc159abc261eea0f4feda611d32eabc35708b74bef6ad44f6c78d5ea0"},
+ {file = "orjson-3.10.3-cp312-none-win_amd64.whl", hash = "sha256:4c895383b1ec42b017dd2c75ae8a5b862fc489006afde06f14afbdd0309b2af0"},
+ {file = "orjson-3.10.3-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:be2719e5041e9fb76c8c2c06b9600fe8e8584e6980061ff88dcbc2691a16d20d"},
+ {file = "orjson-3.10.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0175a5798bdc878956099f5c54b9837cb62cfbf5d0b86ba6d77e43861bcec2"},
+ {file = "orjson-3.10.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:978be58a68ade24f1af7758626806e13cff7748a677faf95fbb298359aa1e20d"},
+ {file = "orjson-3.10.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16bda83b5c61586f6f788333d3cf3ed19015e3b9019188c56983b5a299210eb5"},
+ {file = "orjson-3.10.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ad1f26bea425041e0a1adad34630c4825a9e3adec49079b1fb6ac8d36f8b754"},
+ {file = "orjson-3.10.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:9e253498bee561fe85d6325ba55ff2ff08fb5e7184cd6a4d7754133bd19c9195"},
+ {file = "orjson-3.10.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0a62f9968bab8a676a164263e485f30a0b748255ee2f4ae49a0224be95f4532b"},
+ {file = "orjson-3.10.3-cp38-none-win32.whl", hash = "sha256:8d0b84403d287d4bfa9bf7d1dc298d5c1c5d9f444f3737929a66f2fe4fb8f134"},
+ {file = "orjson-3.10.3-cp38-none-win_amd64.whl", hash = "sha256:8bc7a4df90da5d535e18157220d7915780d07198b54f4de0110eca6b6c11e290"},
+ {file = "orjson-3.10.3-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9059d15c30e675a58fdcd6f95465c1522b8426e092de9fff20edebfdc15e1cb0"},
+ {file = "orjson-3.10.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d40c7f7938c9c2b934b297412c067936d0b54e4b8ab916fd1a9eb8f54c02294"},
+ {file = "orjson-3.10.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d4a654ec1de8fdaae1d80d55cee65893cb06494e124681ab335218be6a0691e7"},
+ {file = "orjson-3.10.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:831c6ef73f9aa53c5f40ae8f949ff7681b38eaddb6904aab89dca4d85099cb78"},
+ {file = "orjson-3.10.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99b880d7e34542db89f48d14ddecbd26f06838b12427d5a25d71baceb5ba119d"},
+ {file = "orjson-3.10.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2e5e176c994ce4bd434d7aafb9ecc893c15f347d3d2bbd8e7ce0b63071c52e25"},
+ {file = "orjson-3.10.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b69a58a37dab856491bf2d3bbf259775fdce262b727f96aafbda359cb1d114d8"},
+ {file = "orjson-3.10.3-cp39-none-win32.whl", hash = "sha256:b8d4d1a6868cde356f1402c8faeb50d62cee765a1f7ffcfd6de732ab0581e063"},
+ {file = "orjson-3.10.3-cp39-none-win_amd64.whl", hash = "sha256:5102f50c5fc46d94f2033fe00d392588564378260d64377aec702f21a7a22912"},
+ {file = "orjson-3.10.3.tar.gz", hash = "sha256:2b166507acae7ba2f7c315dcf185a9111ad5e992ac81f2d507aac39193c2c818"},
+]
+
[[package]]
name = "packaging"
version = "24.0"
@@ -525,6 +941,20 @@ files = [
[package.dependencies]
typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
+[[package]]
+name = "pygments"
+version = "2.18.0"
+description = "Pygments is a syntax highlighting package written in Python."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"},
+ {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"},
+]
+
+[package.extras]
+windows-terminal = ["colorama (>=0.4.6)"]
+
[[package]]
name = "pyopenssl"
version = "24.1.0"
@@ -616,6 +1046,34 @@ pytest = ">=7.0.0"
[package.extras]
test = ["black (>=22.1.0)", "flake8 (>=4.0.1)", "pre-commit (>=2.17.0)", "tox (>=3.24.5)"]
+[[package]]
+name = "python-dotenv"
+version = "1.0.1"
+description = "Read key-value pairs from a .env file and set them as environment variables"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"},
+ {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"},
+]
+
+[package.extras]
+cli = ["click (>=5.0)"]
+
+[[package]]
+name = "python-multipart"
+version = "0.0.9"
+description = "A streaming multipart parser for Python"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "python_multipart-0.0.9-py3-none-any.whl", hash = "sha256:97ca7b8ea7b05f977dc3849c3ba99d51689822fab725c3703af7c866a0c2b215"},
+ {file = "python_multipart-0.0.9.tar.gz", hash = "sha256:03f54688c663f1b7977105f021043b0793151e4cb1c1a9d4a11fc13d622c4026"},
+]
+
+[package.extras]
+dev = ["atomicwrites (==1.4.1)", "attrs (==23.2.0)", "coverage (==7.4.1)", "hatch", "invoke (==2.2.0)", "more-itertools (==10.2.0)", "pbr (==6.0.0)", "pluggy (==1.4.0)", "py (==1.11.0)", "pytest (==8.0.0)", "pytest-cov (==4.1.0)", "pytest-timeout (==2.2.0)", "pyyaml (==6.0.1)", "ruff (==0.2.1)"]
+
[[package]]
name = "pyyaml"
version = "6.0.1"
@@ -641,6 +1099,7 @@ files = [
{file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
{file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
{file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
+ {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"},
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
{file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
{file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
@@ -675,6 +1134,25 @@ files = [
{file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
]
+[[package]]
+name = "rich"
+version = "13.7.1"
+description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
+optional = false
+python-versions = ">=3.7.0"
+files = [
+ {file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"},
+ {file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"},
+]
+
+[package.dependencies]
+markdown-it-py = ">=2.2.0"
+pygments = ">=2.13.0,<3.0.0"
+typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""}
+
+[package.extras]
+jupyter = ["ipywidgets (>=7.5.1,<9)"]
+
[[package]]
name = "ruff"
version = "0.1.15"
@@ -717,6 +1195,28 @@ docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
+[[package]]
+name = "shellingham"
+version = "1.5.4"
+description = "Tool to Detect Surrounding Shell"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"},
+ {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"},
+]
+
+[[package]]
+name = "sniffio"
+version = "1.3.1"
+description = "Sniff out which async library your code is running under"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"},
+ {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
+]
+
[[package]]
name = "soupsieve"
version = "2.5"
@@ -728,6 +1228,24 @@ files = [
{file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"},
]
+[[package]]
+name = "starlette"
+version = "0.37.2"
+description = "The little ASGI library that shines."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "starlette-0.37.2-py3-none-any.whl", hash = "sha256:6fe59f29268538e5d0d182f2791a479a0c64638e6935d1c6989e63fb2699c6ee"},
+ {file = "starlette-0.37.2.tar.gz", hash = "sha256:9af890290133b79fc3db55474ade20f6220a364a0402e0b556e7cd5e1e093823"},
+]
+
+[package.dependencies]
+anyio = ">=3.4.0,<5"
+typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""}
+
+[package.extras]
+full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"]
+
[[package]]
name = "tomli"
version = "2.0.1"
@@ -739,6 +1257,23 @@ files = [
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
]
+[[package]]
+name = "typer"
+version = "0.12.3"
+description = "Typer, build great CLIs. Easy to code. Based on Python type hints."
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "typer-0.12.3-py3-none-any.whl", hash = "sha256:070d7ca53f785acbccba8e7d28b08dcd88f79f1fbda035ade0aecec71ca5c914"},
+ {file = "typer-0.12.3.tar.gz", hash = "sha256:49e73131481d804288ef62598d97a1ceef3058905aa536a1134f90891ba35482"},
+]
+
+[package.dependencies]
+click = ">=8.0.0"
+rich = ">=10.11.0"
+shellingham = ">=1.3.0"
+typing-extensions = ">=3.7.4.3"
+
[[package]]
name = "typing-extensions"
version = "4.11.0"
@@ -750,6 +1285,80 @@ files = [
{file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"},
]
+[[package]]
+name = "ujson"
+version = "5.9.0"
+description = "Ultra fast JSON encoder and decoder for Python"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "ujson-5.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ab71bf27b002eaf7d047c54a68e60230fbd5cd9da60de7ca0aa87d0bccead8fa"},
+ {file = "ujson-5.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a365eac66f5aa7a7fdf57e5066ada6226700884fc7dce2ba5483538bc16c8c5"},
+ {file = "ujson-5.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e015122b337858dba5a3dc3533af2a8fc0410ee9e2374092f6a5b88b182e9fcc"},
+ {file = "ujson-5.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:779a2a88c53039bebfbccca934430dabb5c62cc179e09a9c27a322023f363e0d"},
+ {file = "ujson-5.9.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10ca3c41e80509fd9805f7c149068fa8dbee18872bbdc03d7cca928926a358d5"},
+ {file = "ujson-5.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4a566e465cb2fcfdf040c2447b7dd9718799d0d90134b37a20dff1e27c0e9096"},
+ {file = "ujson-5.9.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:f833c529e922577226a05bc25b6a8b3eb6c4fb155b72dd88d33de99d53113124"},
+ {file = "ujson-5.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b68a0caab33f359b4cbbc10065c88e3758c9f73a11a65a91f024b2e7a1257106"},
+ {file = "ujson-5.9.0-cp310-cp310-win32.whl", hash = "sha256:7cc7e605d2aa6ae6b7321c3ae250d2e050f06082e71ab1a4200b4ae64d25863c"},
+ {file = "ujson-5.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:a6d3f10eb8ccba4316a6b5465b705ed70a06011c6f82418b59278fbc919bef6f"},
+ {file = "ujson-5.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b23bbb46334ce51ddb5dded60c662fbf7bb74a37b8f87221c5b0fec1ec6454b"},
+ {file = "ujson-5.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6974b3a7c17bbf829e6c3bfdc5823c67922e44ff169851a755eab79a3dd31ec0"},
+ {file = "ujson-5.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5964ea916edfe24af1f4cc68488448fbb1ec27a3ddcddc2b236da575c12c8ae"},
+ {file = "ujson-5.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ba7cac47dd65ff88571eceeff48bf30ed5eb9c67b34b88cb22869b7aa19600d"},
+ {file = "ujson-5.9.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6bbd91a151a8f3358c29355a491e915eb203f607267a25e6ab10531b3b157c5e"},
+ {file = "ujson-5.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:829a69d451a49c0de14a9fecb2a2d544a9b2c884c2b542adb243b683a6f15908"},
+ {file = "ujson-5.9.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:a807ae73c46ad5db161a7e883eec0fbe1bebc6a54890152ccc63072c4884823b"},
+ {file = "ujson-5.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8fc2aa18b13d97b3c8ccecdf1a3c405f411a6e96adeee94233058c44ff92617d"},
+ {file = "ujson-5.9.0-cp311-cp311-win32.whl", hash = "sha256:70e06849dfeb2548be48fdd3ceb53300640bc8100c379d6e19d78045e9c26120"},
+ {file = "ujson-5.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:7309d063cd392811acc49b5016728a5e1b46ab9907d321ebbe1c2156bc3c0b99"},
+ {file = "ujson-5.9.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:20509a8c9f775b3a511e308bbe0b72897ba6b800767a7c90c5cca59d20d7c42c"},
+ {file = "ujson-5.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b28407cfe315bd1b34f1ebe65d3bd735d6b36d409b334100be8cdffae2177b2f"},
+ {file = "ujson-5.9.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d302bd17989b6bd90d49bade66943c78f9e3670407dbc53ebcf61271cadc399"},
+ {file = "ujson-5.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f21315f51e0db8ee245e33a649dd2d9dce0594522de6f278d62f15f998e050e"},
+ {file = "ujson-5.9.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5635b78b636a54a86fdbf6f027e461aa6c6b948363bdf8d4fbb56a42b7388320"},
+ {file = "ujson-5.9.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:82b5a56609f1235d72835ee109163c7041b30920d70fe7dac9176c64df87c164"},
+ {file = "ujson-5.9.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:5ca35f484622fd208f55041b042d9d94f3b2c9c5add4e9af5ee9946d2d30db01"},
+ {file = "ujson-5.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:829b824953ebad76d46e4ae709e940bb229e8999e40881338b3cc94c771b876c"},
+ {file = "ujson-5.9.0-cp312-cp312-win32.whl", hash = "sha256:25fa46e4ff0a2deecbcf7100af3a5d70090b461906f2299506485ff31d9ec437"},
+ {file = "ujson-5.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:60718f1720a61560618eff3b56fd517d107518d3c0160ca7a5a66ac949c6cf1c"},
+ {file = "ujson-5.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d581db9db9e41d8ea0b2705c90518ba623cbdc74f8d644d7eb0d107be0d85d9c"},
+ {file = "ujson-5.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ff741a5b4be2d08fceaab681c9d4bc89abf3c9db600ab435e20b9b6d4dfef12e"},
+ {file = "ujson-5.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdcb02cabcb1e44381221840a7af04433c1dc3297af76fde924a50c3054c708c"},
+ {file = "ujson-5.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e208d3bf02c6963e6ef7324dadf1d73239fb7008491fdf523208f60be6437402"},
+ {file = "ujson-5.9.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4b3917296630a075e04d3d07601ce2a176479c23af838b6cf90a2d6b39b0d95"},
+ {file = "ujson-5.9.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0c4d6adb2c7bb9eb7c71ad6f6f612e13b264942e841f8cc3314a21a289a76c4e"},
+ {file = "ujson-5.9.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0b159efece9ab5c01f70b9d10bbb77241ce111a45bc8d21a44c219a2aec8ddfd"},
+ {file = "ujson-5.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f0cb4a7814940ddd6619bdce6be637a4b37a8c4760de9373bac54bb7b229698b"},
+ {file = "ujson-5.9.0-cp38-cp38-win32.whl", hash = "sha256:dc80f0f5abf33bd7099f7ac94ab1206730a3c0a2d17549911ed2cb6b7aa36d2d"},
+ {file = "ujson-5.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:506a45e5fcbb2d46f1a51fead991c39529fc3737c0f5d47c9b4a1d762578fc30"},
+ {file = "ujson-5.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d0fd2eba664a22447102062814bd13e63c6130540222c0aa620701dd01f4be81"},
+ {file = "ujson-5.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bdf7fc21a03bafe4ba208dafa84ae38e04e5d36c0e1c746726edf5392e9f9f36"},
+ {file = "ujson-5.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2f909bc08ce01f122fd9c24bc6f9876aa087188dfaf3c4116fe6e4daf7e194f"},
+ {file = "ujson-5.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd4ea86c2afd41429751d22a3ccd03311c067bd6aeee2d054f83f97e41e11d8f"},
+ {file = "ujson-5.9.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:63fb2e6599d96fdffdb553af0ed3f76b85fda63281063f1cb5b1141a6fcd0617"},
+ {file = "ujson-5.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:32bba5870c8fa2a97f4a68f6401038d3f1922e66c34280d710af00b14a3ca562"},
+ {file = "ujson-5.9.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:37ef92e42535a81bf72179d0e252c9af42a4ed966dc6be6967ebfb929a87bc60"},
+ {file = "ujson-5.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f69f16b8f1c69da00e38dc5f2d08a86b0e781d0ad3e4cc6a13ea033a439c4844"},
+ {file = "ujson-5.9.0-cp39-cp39-win32.whl", hash = "sha256:3382a3ce0ccc0558b1c1668950008cece9bf463ebb17463ebf6a8bfc060dae34"},
+ {file = "ujson-5.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:6adef377ed583477cf005b58c3025051b5faa6b8cc25876e594afbb772578f21"},
+ {file = "ujson-5.9.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ffdfebd819f492e48e4f31c97cb593b9c1a8251933d8f8972e81697f00326ff1"},
+ {file = "ujson-5.9.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4eec2ddc046360d087cf35659c7ba0cbd101f32035e19047013162274e71fcf"},
+ {file = "ujson-5.9.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fbb90aa5c23cb3d4b803c12aa220d26778c31b6e4b7a13a1f49971f6c7d088e"},
+ {file = "ujson-5.9.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba0823cb70866f0d6a4ad48d998dd338dce7314598721bc1b7986d054d782dfd"},
+ {file = "ujson-5.9.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:4e35d7885ed612feb6b3dd1b7de28e89baaba4011ecdf995e88be9ac614765e9"},
+ {file = "ujson-5.9.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b048aa93eace8571eedbd67b3766623e7f0acbf08ee291bef7d8106210432427"},
+ {file = "ujson-5.9.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:323279e68c195110ef85cbe5edce885219e3d4a48705448720ad925d88c9f851"},
+ {file = "ujson-5.9.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9ac92d86ff34296f881e12aa955f7014d276895e0e4e868ba7fddebbde38e378"},
+ {file = "ujson-5.9.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:6eecbd09b316cea1fd929b1e25f70382917542ab11b692cb46ec9b0a26c7427f"},
+ {file = "ujson-5.9.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:473fb8dff1d58f49912323d7cb0859df5585cfc932e4b9c053bf8cf7f2d7c5c4"},
+ {file = "ujson-5.9.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f91719c6abafe429c1a144cfe27883eace9fb1c09a9c5ef1bcb3ae80a3076a4e"},
+ {file = "ujson-5.9.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b1c0991c4fe256f5fdb19758f7eac7f47caac29a6c57d0de16a19048eb86bad"},
+ {file = "ujson-5.9.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a8ea0f55a1396708e564595aaa6696c0d8af532340f477162ff6927ecc46e21"},
+ {file = "ujson-5.9.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:07e0cfdde5fd91f54cd2d7ffb3482c8ff1bf558abf32a8b953a5d169575ae1cd"},
+ {file = "ujson-5.9.0.tar.gz", hash = "sha256:89cc92e73d5501b8a7f48575eeb14ad27156ad092c2e9fc7e3cf949f07e75532"},
+]
+
[[package]]
name = "urllib3"
version = "1.26.18"
@@ -766,6 +1375,76 @@ brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotl
secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"]
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
+[[package]]
+name = "uvicorn"
+version = "0.29.0"
+description = "The lightning-fast ASGI server."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "uvicorn-0.29.0-py3-none-any.whl", hash = "sha256:2c2aac7ff4f4365c206fd773a39bf4ebd1047c238f8b8268ad996829323473de"},
+ {file = "uvicorn-0.29.0.tar.gz", hash = "sha256:6a69214c0b6a087462412670b3ef21224fa48cae0e452b5883e8e8bdfdd11dd0"},
+]
+
+[package.dependencies]
+click = ">=7.0"
+colorama = {version = ">=0.4", optional = true, markers = "sys_platform == \"win32\" and extra == \"standard\""}
+h11 = ">=0.8"
+httptools = {version = ">=0.5.0", optional = true, markers = "extra == \"standard\""}
+python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""}
+pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""}
+typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""}
+uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "(sys_platform != \"win32\" and sys_platform != \"cygwin\") and platform_python_implementation != \"PyPy\" and extra == \"standard\""}
+watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standard\""}
+websockets = {version = ">=10.4", optional = true, markers = "extra == \"standard\""}
+
+[package.extras]
+standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"]
+
+[[package]]
+name = "uvloop"
+version = "0.19.0"
+description = "Fast implementation of asyncio event loop on top of libuv"
+optional = false
+python-versions = ">=3.8.0"
+files = [
+ {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de4313d7f575474c8f5a12e163f6d89c0a878bc49219641d49e6f1444369a90e"},
+ {file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5588bd21cf1fcf06bded085f37e43ce0e00424197e7c10e77afd4bbefffef428"},
+ {file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b1fd71c3843327f3bbc3237bedcdb6504fd50368ab3e04d0410e52ec293f5b8"},
+ {file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a05128d315e2912791de6088c34136bfcdd0c7cbc1cf85fd6fd1bb321b7c849"},
+ {file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:cd81bdc2b8219cb4b2556eea39d2e36bfa375a2dd021404f90a62e44efaaf957"},
+ {file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5f17766fb6da94135526273080f3455a112f82570b2ee5daa64d682387fe0dcd"},
+ {file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4ce6b0af8f2729a02a5d1575feacb2a94fc7b2e983868b009d51c9a9d2149bef"},
+ {file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:31e672bb38b45abc4f26e273be83b72a0d28d074d5b370fc4dcf4c4eb15417d2"},
+ {file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:570fc0ed613883d8d30ee40397b79207eedd2624891692471808a95069a007c1"},
+ {file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5138821e40b0c3e6c9478643b4660bd44372ae1e16a322b8fc07478f92684e24"},
+ {file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:91ab01c6cd00e39cde50173ba4ec68a1e578fee9279ba64f5221810a9e786533"},
+ {file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:47bf3e9312f63684efe283f7342afb414eea4d3011542155c7e625cd799c3b12"},
+ {file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:da8435a3bd498419ee8c13c34b89b5005130a476bda1d6ca8cfdde3de35cd650"},
+ {file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:02506dc23a5d90e04d4f65c7791e65cf44bd91b37f24cfc3ef6cf2aff05dc7ec"},
+ {file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2693049be9d36fef81741fddb3f441673ba12a34a704e7b4361efb75cf30befc"},
+ {file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7010271303961c6f0fe37731004335401eb9075a12680738731e9c92ddd96ad6"},
+ {file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5daa304d2161d2918fa9a17d5635099a2f78ae5b5960e742b2fcfbb7aefaa593"},
+ {file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7207272c9520203fea9b93843bb775d03e1cf88a80a936ce760f60bb5add92f3"},
+ {file = "uvloop-0.19.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:78ab247f0b5671cc887c31d33f9b3abfb88d2614b84e4303f1a63b46c046c8bd"},
+ {file = "uvloop-0.19.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:472d61143059c84947aa8bb74eabbace30d577a03a1805b77933d6bd13ddebbd"},
+ {file = "uvloop-0.19.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45bf4c24c19fb8a50902ae37c5de50da81de4922af65baf760f7c0c42e1088be"},
+ {file = "uvloop-0.19.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271718e26b3e17906b28b67314c45d19106112067205119dddbd834c2b7ce797"},
+ {file = "uvloop-0.19.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:34175c9fd2a4bc3adc1380e1261f60306344e3407c20a4d684fd5f3be010fa3d"},
+ {file = "uvloop-0.19.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e27f100e1ff17f6feeb1f33968bc185bf8ce41ca557deee9d9bbbffeb72030b7"},
+ {file = "uvloop-0.19.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:13dfdf492af0aa0a0edf66807d2b465607d11c4fa48f4a1fd41cbea5b18e8e8b"},
+ {file = "uvloop-0.19.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6e3d4e85ac060e2342ff85e90d0c04157acb210b9ce508e784a944f852a40e67"},
+ {file = "uvloop-0.19.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ca4956c9ab567d87d59d49fa3704cf29e37109ad348f2d5223c9bf761a332e7"},
+ {file = "uvloop-0.19.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f467a5fd23b4fc43ed86342641f3936a68ded707f4627622fa3f82a120e18256"},
+ {file = "uvloop-0.19.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:492e2c32c2af3f971473bc22f086513cedfc66a130756145a931a90c3958cb17"},
+ {file = "uvloop-0.19.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2df95fca285a9f5bfe730e51945ffe2fa71ccbfdde3b0da5772b4ee4f2e770d5"},
+ {file = "uvloop-0.19.0.tar.gz", hash = "sha256:0246f4fd1bf2bf702e06b0d45ee91677ee5c31242f39aab4ea6fe0c51aedd0fd"},
+]
+
+[package.extras]
+docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"]
+test = ["Cython (>=0.29.36,<0.30.0)", "aiohttp (==3.9.0b0)", "aiohttp (>=3.8.1)", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=23.0.0,<23.1.0)", "pycodestyle (>=2.9.0,<2.10.0)"]
+
[[package]]
name = "virtualenv"
version = "20.25.3"
@@ -786,7 +1465,175 @@ platformdirs = ">=3.9.1,<5"
docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"]
test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"]
+[[package]]
+name = "watchfiles"
+version = "0.21.0"
+description = "Simple, modern and high performance file watching and code reload in python."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "watchfiles-0.21.0-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:27b4035013f1ea49c6c0b42d983133b136637a527e48c132d368eb19bf1ac6aa"},
+ {file = "watchfiles-0.21.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c81818595eff6e92535ff32825f31c116f867f64ff8cdf6562cd1d6b2e1e8f3e"},
+ {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6c107ea3cf2bd07199d66f156e3ea756d1b84dfd43b542b2d870b77868c98c03"},
+ {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d9ac347653ebd95839a7c607608703b20bc07e577e870d824fa4801bc1cb124"},
+ {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5eb86c6acb498208e7663ca22dbe68ca2cf42ab5bf1c776670a50919a56e64ab"},
+ {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f564bf68404144ea6b87a78a3f910cc8de216c6b12a4cf0b27718bf4ec38d303"},
+ {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d0f32ebfaa9c6011f8454994f86108c2eb9c79b8b7de00b36d558cadcedaa3d"},
+ {file = "watchfiles-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6d45d9b699ecbac6c7bd8e0a2609767491540403610962968d258fd6405c17c"},
+ {file = "watchfiles-0.21.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:aff06b2cac3ef4616e26ba17a9c250c1fe9dd8a5d907d0193f84c499b1b6e6a9"},
+ {file = "watchfiles-0.21.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d9792dff410f266051025ecfaa927078b94cc7478954b06796a9756ccc7e14a9"},
+ {file = "watchfiles-0.21.0-cp310-none-win32.whl", hash = "sha256:214cee7f9e09150d4fb42e24919a1e74d8c9b8a9306ed1474ecaddcd5479c293"},
+ {file = "watchfiles-0.21.0-cp310-none-win_amd64.whl", hash = "sha256:1ad7247d79f9f55bb25ab1778fd47f32d70cf36053941f07de0b7c4e96b5d235"},
+ {file = "watchfiles-0.21.0-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:668c265d90de8ae914f860d3eeb164534ba2e836811f91fecc7050416ee70aa7"},
+ {file = "watchfiles-0.21.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a23092a992e61c3a6a70f350a56db7197242f3490da9c87b500f389b2d01eef"},
+ {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e7941bbcfdded9c26b0bf720cb7e6fd803d95a55d2c14b4bd1f6a2772230c586"},
+ {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11cd0c3100e2233e9c53106265da31d574355c288e15259c0d40a4405cbae317"},
+ {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d78f30cbe8b2ce770160d3c08cff01b2ae9306fe66ce899b73f0409dc1846c1b"},
+ {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6674b00b9756b0af620aa2a3346b01f8e2a3dc729d25617e1b89cf6af4a54eb1"},
+ {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fd7ac678b92b29ba630d8c842d8ad6c555abda1b9ef044d6cc092dacbfc9719d"},
+ {file = "watchfiles-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c873345680c1b87f1e09e0eaf8cf6c891b9851d8b4d3645e7efe2ec20a20cc7"},
+ {file = "watchfiles-0.21.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:49f56e6ecc2503e7dbe233fa328b2be1a7797d31548e7a193237dcdf1ad0eee0"},
+ {file = "watchfiles-0.21.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:02d91cbac553a3ad141db016e3350b03184deaafeba09b9d6439826ee594b365"},
+ {file = "watchfiles-0.21.0-cp311-none-win32.whl", hash = "sha256:ebe684d7d26239e23d102a2bad2a358dedf18e462e8808778703427d1f584400"},
+ {file = "watchfiles-0.21.0-cp311-none-win_amd64.whl", hash = "sha256:4566006aa44cb0d21b8ab53baf4b9c667a0ed23efe4aaad8c227bfba0bf15cbe"},
+ {file = "watchfiles-0.21.0-cp311-none-win_arm64.whl", hash = "sha256:c550a56bf209a3d987d5a975cdf2063b3389a5d16caf29db4bdddeae49f22078"},
+ {file = "watchfiles-0.21.0-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:51ddac60b96a42c15d24fbdc7a4bfcd02b5a29c047b7f8bf63d3f6f5a860949a"},
+ {file = "watchfiles-0.21.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:511f0b034120cd1989932bf1e9081aa9fb00f1f949fbd2d9cab6264916ae89b1"},
+ {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:cfb92d49dbb95ec7a07511bc9efb0faff8fe24ef3805662b8d6808ba8409a71a"},
+ {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f92944efc564867bbf841c823c8b71bb0be75e06b8ce45c084b46411475a915"},
+ {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:642d66b75eda909fd1112d35c53816d59789a4b38c141a96d62f50a3ef9b3360"},
+ {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d23bcd6c8eaa6324fe109d8cac01b41fe9a54b8c498af9ce464c1aeeb99903d6"},
+ {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18d5b4da8cf3e41895b34e8c37d13c9ed294954907929aacd95153508d5d89d7"},
+ {file = "watchfiles-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b8d1eae0f65441963d805f766c7e9cd092f91e0c600c820c764a4ff71a0764c"},
+ {file = "watchfiles-0.21.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1fd9a5205139f3c6bb60d11f6072e0552f0a20b712c85f43d42342d162be1235"},
+ {file = "watchfiles-0.21.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a1e3014a625bcf107fbf38eece0e47fa0190e52e45dc6eee5a8265ddc6dc5ea7"},
+ {file = "watchfiles-0.21.0-cp312-none-win32.whl", hash = "sha256:9d09869f2c5a6f2d9df50ce3064b3391d3ecb6dced708ad64467b9e4f2c9bef3"},
+ {file = "watchfiles-0.21.0-cp312-none-win_amd64.whl", hash = "sha256:18722b50783b5e30a18a8a5db3006bab146d2b705c92eb9a94f78c72beb94094"},
+ {file = "watchfiles-0.21.0-cp312-none-win_arm64.whl", hash = "sha256:a3b9bec9579a15fb3ca2d9878deae789df72f2b0fdaf90ad49ee389cad5edab6"},
+ {file = "watchfiles-0.21.0-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:4ea10a29aa5de67de02256a28d1bf53d21322295cb00bd2d57fcd19b850ebd99"},
+ {file = "watchfiles-0.21.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:40bca549fdc929b470dd1dbfcb47b3295cb46a6d2c90e50588b0a1b3bd98f429"},
+ {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9b37a7ba223b2f26122c148bb8d09a9ff312afca998c48c725ff5a0a632145f7"},
+ {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec8c8900dc5c83650a63dd48c4d1d245343f904c4b64b48798c67a3767d7e165"},
+ {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8ad3fe0a3567c2f0f629d800409cd528cb6251da12e81a1f765e5c5345fd0137"},
+ {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d353c4cfda586db2a176ce42c88f2fc31ec25e50212650c89fdd0f560ee507b"},
+ {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:83a696da8922314ff2aec02987eefb03784f473281d740bf9170181829133765"},
+ {file = "watchfiles-0.21.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a03651352fc20975ee2a707cd2d74a386cd303cc688f407296064ad1e6d1562"},
+ {file = "watchfiles-0.21.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3ad692bc7792be8c32918c699638b660c0de078a6cbe464c46e1340dadb94c19"},
+ {file = "watchfiles-0.21.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06247538e8253975bdb328e7683f8515ff5ff041f43be6c40bff62d989b7d0b0"},
+ {file = "watchfiles-0.21.0-cp38-none-win32.whl", hash = "sha256:9a0aa47f94ea9a0b39dd30850b0adf2e1cd32a8b4f9c7aa443d852aacf9ca214"},
+ {file = "watchfiles-0.21.0-cp38-none-win_amd64.whl", hash = "sha256:8d5f400326840934e3507701f9f7269247f7c026d1b6cfd49477d2be0933cfca"},
+ {file = "watchfiles-0.21.0-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:7f762a1a85a12cc3484f77eee7be87b10f8c50b0b787bb02f4e357403cad0c0e"},
+ {file = "watchfiles-0.21.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6e9be3ef84e2bb9710f3f777accce25556f4a71e15d2b73223788d528fcc2052"},
+ {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4c48a10d17571d1275701e14a601e36959ffada3add8cdbc9e5061a6e3579a5d"},
+ {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c889025f59884423428c261f212e04d438de865beda0b1e1babab85ef4c0f01"},
+ {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:66fac0c238ab9a2e72d026b5fb91cb902c146202bbd29a9a1a44e8db7b710b6f"},
+ {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b4a21f71885aa2744719459951819e7bf5a906a6448a6b2bbce8e9cc9f2c8128"},
+ {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c9198c989f47898b2c22201756f73249de3748e0fc9de44adaf54a8b259cc0c"},
+ {file = "watchfiles-0.21.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8f57c4461cd24fda22493109c45b3980863c58a25b8bec885ca8bea6b8d4b28"},
+ {file = "watchfiles-0.21.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:853853cbf7bf9408b404754b92512ebe3e3a83587503d766d23e6bf83d092ee6"},
+ {file = "watchfiles-0.21.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d5b1dc0e708fad9f92c296ab2f948af403bf201db8fb2eb4c8179db143732e49"},
+ {file = "watchfiles-0.21.0-cp39-none-win32.whl", hash = "sha256:59137c0c6826bd56c710d1d2bda81553b5e6b7c84d5a676747d80caf0409ad94"},
+ {file = "watchfiles-0.21.0-cp39-none-win_amd64.whl", hash = "sha256:6cb8fdc044909e2078c248986f2fc76f911f72b51ea4a4fbbf472e01d14faa58"},
+ {file = "watchfiles-0.21.0-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:ab03a90b305d2588e8352168e8c5a1520b721d2d367f31e9332c4235b30b8994"},
+ {file = "watchfiles-0.21.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:927c589500f9f41e370b0125c12ac9e7d3a2fd166b89e9ee2828b3dda20bfe6f"},
+ {file = "watchfiles-0.21.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bd467213195e76f838caf2c28cd65e58302d0254e636e7c0fca81efa4a2e62c"},
+ {file = "watchfiles-0.21.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02b73130687bc3f6bb79d8a170959042eb56eb3a42df3671c79b428cd73f17cc"},
+ {file = "watchfiles-0.21.0-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:08dca260e85ffae975448e344834d765983237ad6dc308231aa16e7933db763e"},
+ {file = "watchfiles-0.21.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:3ccceb50c611c433145502735e0370877cced72a6c70fd2410238bcbc7fe51d8"},
+ {file = "watchfiles-0.21.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57d430f5fb63fea141ab71ca9c064e80de3a20b427ca2febcbfcef70ff0ce895"},
+ {file = "watchfiles-0.21.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dd5fad9b9c0dd89904bbdea978ce89a2b692a7ee8a0ce19b940e538c88a809c"},
+ {file = "watchfiles-0.21.0-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:be6dd5d52b73018b21adc1c5d28ac0c68184a64769052dfeb0c5d9998e7f56a2"},
+ {file = "watchfiles-0.21.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:b3cab0e06143768499384a8a5efb9c4dc53e19382952859e4802f294214f36ec"},
+ {file = "watchfiles-0.21.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c6ed10c2497e5fedadf61e465b3ca12a19f96004c15dcffe4bd442ebadc2d85"},
+ {file = "watchfiles-0.21.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43babacef21c519bc6631c5fce2a61eccdfc011b4bcb9047255e9620732c8097"},
+ {file = "watchfiles-0.21.0.tar.gz", hash = "sha256:c76c635fabf542bb78524905718c39f736a98e5ab25b23ec6d4abede1a85a6a3"},
+]
+
+[package.dependencies]
+anyio = ">=3.0.0"
+
+[[package]]
+name = "websockets"
+version = "12.0"
+description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "websockets-12.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d554236b2a2006e0ce16315c16eaa0d628dab009c33b63ea03f41c6107958374"},
+ {file = "websockets-12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2d225bb6886591b1746b17c0573e29804619c8f755b5598d875bb4235ea639be"},
+ {file = "websockets-12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eb809e816916a3b210bed3c82fb88eaf16e8afcf9c115ebb2bacede1797d2547"},
+ {file = "websockets-12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c588f6abc13f78a67044c6b1273a99e1cf31038ad51815b3b016ce699f0d75c2"},
+ {file = "websockets-12.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5aa9348186d79a5f232115ed3fa9020eab66d6c3437d72f9d2c8ac0c6858c558"},
+ {file = "websockets-12.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6350b14a40c95ddd53e775dbdbbbc59b124a5c8ecd6fbb09c2e52029f7a9f480"},
+ {file = "websockets-12.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:70ec754cc2a769bcd218ed8d7209055667b30860ffecb8633a834dde27d6307c"},
+ {file = "websockets-12.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6e96f5ed1b83a8ddb07909b45bd94833b0710f738115751cdaa9da1fb0cb66e8"},
+ {file = "websockets-12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4d87be612cbef86f994178d5186add3d94e9f31cc3cb499a0482b866ec477603"},
+ {file = "websockets-12.0-cp310-cp310-win32.whl", hash = "sha256:befe90632d66caaf72e8b2ed4d7f02b348913813c8b0a32fae1cc5fe3730902f"},
+ {file = "websockets-12.0-cp310-cp310-win_amd64.whl", hash = "sha256:363f57ca8bc8576195d0540c648aa58ac18cf85b76ad5202b9f976918f4219cf"},
+ {file = "websockets-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5d873c7de42dea355d73f170be0f23788cf3fa9f7bed718fd2830eefedce01b4"},
+ {file = "websockets-12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3f61726cae9f65b872502ff3c1496abc93ffbe31b278455c418492016e2afc8f"},
+ {file = "websockets-12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed2fcf7a07334c77fc8a230755c2209223a7cc44fc27597729b8ef5425aa61a3"},
+ {file = "websockets-12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e332c210b14b57904869ca9f9bf4ca32f5427a03eeb625da9b616c85a3a506c"},
+ {file = "websockets-12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5693ef74233122f8ebab026817b1b37fe25c411ecfca084b29bc7d6efc548f45"},
+ {file = "websockets-12.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e9e7db18b4539a29cc5ad8c8b252738a30e2b13f033c2d6e9d0549b45841c04"},
+ {file = "websockets-12.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6e2df67b8014767d0f785baa98393725739287684b9f8d8a1001eb2839031447"},
+ {file = "websockets-12.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bea88d71630c5900690fcb03161ab18f8f244805c59e2e0dc4ffadae0a7ee0ca"},
+ {file = "websockets-12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dff6cdf35e31d1315790149fee351f9e52978130cef6c87c4b6c9b3baf78bc53"},
+ {file = "websockets-12.0-cp311-cp311-win32.whl", hash = "sha256:3e3aa8c468af01d70332a382350ee95f6986db479ce7af14d5e81ec52aa2b402"},
+ {file = "websockets-12.0-cp311-cp311-win_amd64.whl", hash = "sha256:25eb766c8ad27da0f79420b2af4b85d29914ba0edf69f547cc4f06ca6f1d403b"},
+ {file = "websockets-12.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0e6e2711d5a8e6e482cacb927a49a3d432345dfe7dea8ace7b5790df5932e4df"},
+ {file = "websockets-12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:dbcf72a37f0b3316e993e13ecf32f10c0e1259c28ffd0a85cee26e8549595fbc"},
+ {file = "websockets-12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12743ab88ab2af1d17dd4acb4645677cb7063ef4db93abffbf164218a5d54c6b"},
+ {file = "websockets-12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b645f491f3c48d3f8a00d1fce07445fab7347fec54a3e65f0725d730d5b99cb"},
+ {file = "websockets-12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9893d1aa45a7f8b3bc4510f6ccf8db8c3b62120917af15e3de247f0780294b92"},
+ {file = "websockets-12.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f38a7b376117ef7aff996e737583172bdf535932c9ca021746573bce40165ed"},
+ {file = "websockets-12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f764ba54e33daf20e167915edc443b6f88956f37fb606449b4a5b10ba42235a5"},
+ {file = "websockets-12.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1e4b3f8ea6a9cfa8be8484c9221ec0257508e3a1ec43c36acdefb2a9c3b00aa2"},
+ {file = "websockets-12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9fdf06fd06c32205a07e47328ab49c40fc1407cdec801d698a7c41167ea45113"},
+ {file = "websockets-12.0-cp312-cp312-win32.whl", hash = "sha256:baa386875b70cbd81798fa9f71be689c1bf484f65fd6fb08d051a0ee4e79924d"},
+ {file = "websockets-12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae0a5da8f35a5be197f328d4727dbcfafa53d1824fac3d96cdd3a642fe09394f"},
+ {file = "websockets-12.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5f6ffe2c6598f7f7207eef9a1228b6f5c818f9f4d53ee920aacd35cec8110438"},
+ {file = "websockets-12.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9edf3fc590cc2ec20dc9d7a45108b5bbaf21c0d89f9fd3fd1685e223771dc0b2"},
+ {file = "websockets-12.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8572132c7be52632201a35f5e08348137f658e5ffd21f51f94572ca6c05ea81d"},
+ {file = "websockets-12.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:604428d1b87edbf02b233e2c207d7d528460fa978f9e391bd8aaf9c8311de137"},
+ {file = "websockets-12.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a9d160fd080c6285e202327aba140fc9a0d910b09e423afff4ae5cbbf1c7205"},
+ {file = "websockets-12.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87b4aafed34653e465eb77b7c93ef058516cb5acf3eb21e42f33928616172def"},
+ {file = "websockets-12.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b2ee7288b85959797970114deae81ab41b731f19ebcd3bd499ae9ca0e3f1d2c8"},
+ {file = "websockets-12.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7fa3d25e81bfe6a89718e9791128398a50dec6d57faf23770787ff441d851967"},
+ {file = "websockets-12.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a571f035a47212288e3b3519944f6bf4ac7bc7553243e41eac50dd48552b6df7"},
+ {file = "websockets-12.0-cp38-cp38-win32.whl", hash = "sha256:3c6cc1360c10c17463aadd29dd3af332d4a1adaa8796f6b0e9f9df1fdb0bad62"},
+ {file = "websockets-12.0-cp38-cp38-win_amd64.whl", hash = "sha256:1bf386089178ea69d720f8db6199a0504a406209a0fc23e603b27b300fdd6892"},
+ {file = "websockets-12.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ab3d732ad50a4fbd04a4490ef08acd0517b6ae6b77eb967251f4c263011a990d"},
+ {file = "websockets-12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1d9697f3337a89691e3bd8dc56dea45a6f6d975f92e7d5f773bc715c15dde28"},
+ {file = "websockets-12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1df2fbd2c8a98d38a66f5238484405b8d1d16f929bb7a33ed73e4801222a6f53"},
+ {file = "websockets-12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23509452b3bc38e3a057382c2e941d5ac2e01e251acce7adc74011d7d8de434c"},
+ {file = "websockets-12.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e5fc14ec6ea568200ea4ef46545073da81900a2b67b3e666f04adf53ad452ec"},
+ {file = "websockets-12.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46e71dbbd12850224243f5d2aeec90f0aaa0f2dde5aeeb8fc8df21e04d99eff9"},
+ {file = "websockets-12.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b81f90dcc6c85a9b7f29873beb56c94c85d6f0dac2ea8b60d995bd18bf3e2aae"},
+ {file = "websockets-12.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a02413bc474feda2849c59ed2dfb2cddb4cd3d2f03a2fedec51d6e959d9b608b"},
+ {file = "websockets-12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bbe6013f9f791944ed31ca08b077e26249309639313fff132bfbf3ba105673b9"},
+ {file = "websockets-12.0-cp39-cp39-win32.whl", hash = "sha256:cbe83a6bbdf207ff0541de01e11904827540aa069293696dd528a6640bd6a5f6"},
+ {file = "websockets-12.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc4e7fa5414512b481a2483775a8e8be7803a35b30ca805afa4998a84f9fd9e8"},
+ {file = "websockets-12.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:248d8e2446e13c1d4326e0a6a4e9629cb13a11195051a73acf414812700badbd"},
+ {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f44069528d45a933997a6fef143030d8ca8042f0dfaad753e2906398290e2870"},
+ {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c4e37d36f0d19f0a4413d3e18c0d03d0c268ada2061868c1e6f5ab1a6d575077"},
+ {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d829f975fc2e527a3ef2f9c8f25e553eb7bc779c6665e8e1d52aa22800bb38b"},
+ {file = "websockets-12.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2c71bd45a777433dd9113847af751aae36e448bc6b8c361a566cb043eda6ec30"},
+ {file = "websockets-12.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0bee75f400895aef54157b36ed6d3b308fcab62e5260703add87f44cee9c82a6"},
+ {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:423fc1ed29f7512fceb727e2d2aecb952c46aa34895e9ed96071821309951123"},
+ {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27a5e9964ef509016759f2ef3f2c1e13f403725a5e6a1775555994966a66e931"},
+ {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3181df4583c4d3994d31fb235dc681d2aaad744fbdbf94c4802485ececdecf2"},
+ {file = "websockets-12.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b067cb952ce8bf40115f6c19f478dc71c5e719b7fbaa511359795dfd9d1a6468"},
+ {file = "websockets-12.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:00700340c6c7ab788f176d118775202aadea7602c5cc6be6ae127761c16d6b0b"},
+ {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e469d01137942849cff40517c97a30a93ae79917752b34029f0ec72df6b46399"},
+ {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffefa1374cd508d633646d51a8e9277763a9b78ae71324183693959cf94635a7"},
+ {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba0cab91b3956dfa9f512147860783a1829a8d905ee218a9837c18f683239611"},
+ {file = "websockets-12.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2cb388a5bfb56df4d9a406783b7f9dbefb888c09b71629351cc6b036e9259370"},
+ {file = "websockets-12.0-py3-none-any.whl", hash = "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e"},
+ {file = "websockets-12.0.tar.gz", hash = "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b"},
+]
+
[metadata]
lock-version = "2.0"
python-versions = ">=3.8"
-content-hash = "72b7362f5f84bff8c84aefc41e8ea1747692ae0c66d4e0c0169ceda9bea67196"
+content-hash = "e17c06bc0dfb64dc90ed83f7fd1bee2f74204b8bcc5539a6e3a0cac6b8f90b3c"
diff --git a/pyproject.toml b/pyproject.toml
index aabe4ae..024b4b1 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -34,6 +34,8 @@ urllib3 = "1.26.18"
pyopenssl = "24.1.0"
beautifulsoup4 = "4.12.3"
pydantic = ">=2.0.0"
+fastapi = "^0.111.0"
+uvicorn = "^0.29.0"
[tool.poetry.dev-dependencies]
pytest = "^7.4.3"
From cf14f2bb24eca7472014e470ddc1a1164be259ef Mon Sep 17 00:00:00 2001
From: longsihua2003 <1161443436@qq.com>
Date: Tue, 7 May 2024 23:30:00 +0800
Subject: [PATCH 4/6] ecjtu-api end
---
ecjtu/client.py | 15 +-
ecjtu/ecjtu_api/api.py | 83 ++++++---
ecjtu/ecjtu_api/auth.py | 111 +++++++++++-
ecjtu/ecjtu_api/ecjtu_schema.py | 6 -
ecjtu/ecjtu_api/middle.py | 14 +-
ecjtu/ecjtu_api/respose_result.py | 8 +
ecjtu/ecjtu_api/schema.py | 22 +++
ecjtu/utils/cookie.py | 23 +++
ecjtu/utils/logger.py | 5 +
poetry.lock | 286 ++++++++++++++++--------------
pyproject.toml | 3 +
11 files changed, 393 insertions(+), 183 deletions(-)
delete mode 100644 ecjtu/ecjtu_api/ecjtu_schema.py
create mode 100644 ecjtu/ecjtu_api/schema.py
create mode 100644 ecjtu/utils/cookie.py
diff --git a/ecjtu/client.py b/ecjtu/client.py
index 97452a2..71fff40 100644
--- a/ecjtu/client.py
+++ b/ecjtu/client.py
@@ -63,7 +63,11 @@ def has_login(self) -> bool:
class ECJTU(BaseClient[httpx.Client], httpx.Client):
def __init__(
- self, stud_id: Optional[str] = None, password: Optional[str] = None, **kwargs
+ self,
+ stud_id: Optional[str] = None,
+ password: Optional[str] = None,
+ cookie: Optional[CookieTypes] = None,
+ **kwargs,
) -> None:
"""Initialize ECJTU client.
@@ -73,9 +77,12 @@ def __init__(
"""
super().__init__(verify=False, **kwargs)
- self.stud_id: str = stud_id or os.environ.get("ECJTU_STUDENT_ID")
- self.password: str = password or os.environ.get("ECJTU_PASSWORD")
- self.enc_password: str = _get_enc_password(self.password)
+ if cookie:
+ self.cookies = cookie
+ else:
+ self.stud_id: str = stud_id or os.environ.get("ECJTU_STUDENT_ID")
+ self.password: str = password or os.environ.get("ECJTU_PASSWORD")
+ self.enc_password: str = _get_enc_password(self.password)
self.scheduled_courses = crud.ScheduledCourseCRUD(self)
self.scores = crud.ScoreCRUD(self)
diff --git a/ecjtu/ecjtu_api/api.py b/ecjtu/ecjtu_api/api.py
index 2db1afb..6e9b5cf 100644
--- a/ecjtu/ecjtu_api/api.py
+++ b/ecjtu/ecjtu_api/api.py
@@ -1,9 +1,12 @@
+import datetime
+import re
+
from fastapi import FastAPI, Header
from fastapi.responses import RedirectResponse
from ecjtu.client import ECJTU
-from . import auth, ecjtu_schema, middle, respose_result
+from . import auth, middle, respose_result, schema
app = FastAPI(title="ECJTU API", description="API for ECJTU")
@@ -22,21 +25,37 @@ def push_docs():
summary="登录",
description="登录并获取token,以下所有接口都需要token才可以使用",
)
-def login(user: ecjtu_schema.UserLoginSchema):
- client = ECJTU(user.stud_id, user.password)
+def login(user: schema.UserLoginSchema):
+ try:
+ access_token, refresh_token = auth.create_tokens(user.stud_id, user.password)
+ except Exception as e:
+ return respose_result.ResponseResult.error(str(e))
+
+ return respose_result.ResponseResult.success(
+ {"access_token": access_token, "refresh_token": refresh_token}
+ )
+
+
+@app.post(
+ "/refresh_token",
+ tags=["登录"],
+ summary="刷新access_token",
+ description="刷新access_token",
+)
+def refresh_token(data: str = None):
try:
- client.login()
+ access_token = auth.refresh_access_token(data)
except Exception as e:
return respose_result.ResponseResult.error(str(e))
- token = auth.encode(user.stud_id, user.password)
- return respose_result.ResponseResult.success({"token": token})
+ return respose_result.ResponseResult.success({"access_token": access_token})
# gpa接口
@app.get("/gpa", tags=["GPA"], summary="获取GPA", description="获取当学期GPA")
def gpa(token: str = Header(None)):
- stud_id, password = auth.decode(token)
- client = ECJTU(stud_id, password)
+ stud_id = auth.get_stud_id(token)
+ cookie = auth.get_cookie(stud_id)
+ client = ECJTU(cookie=cookie)
try:
gpa = client.gpa.today()
except Exception as e:
@@ -47,8 +66,9 @@ def gpa(token: str = Header(None)):
# 课表接口
@app.get("/schedule", tags=["课表"], summary="获取当天课表", description="获取当天课表")
def schedule(token: str = Header(None)):
- stud_id, password = auth.decode(token)
- client = ECJTU(stud_id, password)
+ stud_id = auth.get_stud_id(token)
+ cookie = auth.get_cookie(stud_id)
+ client = ECJTU(cookie=cookie)
try:
schedule = client.scheduled_courses.today()
except Exception as e:
@@ -67,10 +87,16 @@ def schedule(token: str = Header(None)):
)
def schedule_date(token: str = Header(None), date: str = None):
# date(str): The date to filter, eg: 2023-01-01
- stud_id, password = auth.decode(token)
- client = ECJTU(stud_id, password)
try:
- scheduled_courses = client.scheduled_courses.filter(date=date)
+ valid_date = datetime.datetime.strptime(date, "%Y-%m-%d").date()
+ except ValueError:
+ return respose_result.ResponseResult.param_error("日期格式错误")
+
+ stud_id = auth.get_stud_id(token)
+ cookie = auth.get_cookie(stud_id)
+ client = ECJTU(cookie=cookie)
+ try:
+ scheduled_courses = client.scheduled_courses.filter(date=valid_date)
except Exception as e:
return respose_result.ResponseResult.error(str(e))
schedule_list = []
@@ -80,11 +106,12 @@ def schedule_date(token: str = Header(None), date: str = None):
@app.get(
- "/schedule/week", tags=["课表"], summary="获取本周课表", description="获取本周课表"
+ "/schedule_week", tags=["课表"], summary="获取本周课表", description="获取本周课表"
)
def schedule_week(token: str = Header(None)):
- stud_id, password = auth.decode(token)
- client = ECJTU(stud_id, password)
+ stud_id = auth.get_stud_id(token)
+ cookie = auth.get_cookie(stud_id)
+ client = ECJTU(cookie=cookie)
try:
schedule = client.scheduled_courses.this_week()
except Exception as e:
@@ -101,8 +128,9 @@ def schedule_week(token: str = Header(None)):
# 成绩接口
@app.get("/score", tags=["成绩"], summary="获取当前成绩", description="获取当学期成绩")
def score(token: str = Header(None)):
- stud_id, password = auth.decode(token)
- client = ECJTU(stud_id, password)
+ stud_id = auth.get_stud_id(token)
+ cookie = auth.get_cookie(stud_id)
+ client = ECJTU(cookie=cookie)
try:
score = client.scores.today()
except Exception as e:
@@ -121,8 +149,11 @@ def score(token: str = Header(None)):
)
def score_semester(token: str = Header(None), semester: str = None):
# semester(Optional[str]): The semester to filter, eg: 2023.1, 2023.2
- stud_id, password = auth.decode(token)
- client = ECJTU(stud_id, password)
+ if not re.match(r"\d{4}\.[12]", semester):
+ return respose_result.ResponseResult.param_error("学期格式错误")
+ stud_id = auth.get_stud_id(token)
+ cookie = auth.get_cookie(stud_id)
+ client = ECJTU(cookie=cookie)
try:
scores = client.scores.filter(semester=semester)
except Exception as e:
@@ -141,8 +172,9 @@ def score_semester(token: str = Header(None), semester: str = None):
description="获取当前学期选课情况",
)
def elective_courses(token: str = Header(None)):
- stud_id, password = auth.decode(token)
- client = ECJTU(stud_id, password)
+ stud_id = auth.get_stud_id(token)
+ cookie = auth.get_cookie(stud_id)
+ client = ECJTU(cookie=cookie)
try:
elective_courses = client.elective_courses.today()
except Exception as e:
@@ -161,8 +193,11 @@ def elective_courses(token: str = Header(None)):
)
def elective_courses_semester(token: str = Header(None), semester: str = None):
# semester(Optional[str]): The semester to filter, eg: 2023.1, 2023.2
- stud_id, password = auth.decode(token)
- client = ECJTU(stud_id, password)
+ if not re.match(r"\d{4}\.[12]", semester):
+ return respose_result.ResponseResult.param_error("学期格式错误")
+ stud_id = auth.get_stud_id(token)
+ cookie = auth.get_cookie(stud_id)
+ client = ECJTU(cookie=cookie)
try:
elective_courses = client.elective_courses.filter(semester=semester)
except Exception as e:
diff --git a/ecjtu/ecjtu_api/auth.py b/ecjtu/ecjtu_api/auth.py
index 8ba0050..193e5d3 100644
--- a/ecjtu/ecjtu_api/auth.py
+++ b/ecjtu/ecjtu_api/auth.py
@@ -1,14 +1,109 @@
import base64
+import datetime
-KEY = "zxcvbnmasdgfhjklpoiuytrewq"
+from cushy_storage import CushyOrmCache
+from ecjtu.client import ECJTU
+from ecjtu.utils.cookie import cookies_tolist, list_tocookie
+from ecjtu.utils.logger import get_path
-def encode(stud_id, pwd):
- # enc_pwd = _get_enc_password(pwd);
- token = base64.b64encode(f"{stud_id}:{pwd}".encode()).decode()
- return token
+from . import schema
-def decode(token):
- stud_id, enc_pwd = base64.b64decode(token.encode()).decode().split(":")
- return stud_id, enc_pwd
+def encode_data(data):
+ # 将数据编码为base64字符串
+ return base64.b64encode(data.encode()).decode()
+
+
+def decode_data(encoded_data):
+ # 解码base64字符串
+ return base64.b64decode(encoded_data.encode()).decode()
+
+
+# 双token加密
+def create_tokens(stud_id, pwd):
+ access_time = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(
+ minutes=60
+ )
+ access_data = f"{stud_id}:access_token:{access_time}"
+ refresh_time = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(
+ days=7
+ )
+ refresh_data = f"{stud_id}:{pwd}:{refresh_time}"
+
+ access_token = encode_data(access_data)
+ refresh_token = encode_data(refresh_data)
+ client = ECJTU(stud_id, pwd)
+ try:
+ client.login()
+ except Exception as e:
+ raise e
+
+ cookie_list = cookies_tolist(client.cookies)
+ stud_file = CushyOrmCache(get_path())
+ stud = stud_file.query("FileAuth").filter(stud_id=stud_id).first()
+
+ # 创建用户储存信息
+ if not stud:
+ stud = schema.FileAuth(stud_id, access_token, cookie_list)
+ stud_file.add(stud)
+ return access_token, refresh_token
+ # 更新用户信息
+ stud.cookie = cookie_list
+ stud.token = access_token
+ stud_file.update_obj(stud)
+ return access_token, refresh_token
+
+
+# 刷新access_token
+def refresh_access_token(refresh_token):
+ data = decode_data(refresh_token)
+ print(data)
+ stud_id = data.split(":")[0]
+ pwd = data.split(":")[1]
+ token_time = data.split(":")[2]
+
+ expire_time = datetime.datetime.fromisoformat(token_time)
+ expire_time = expire_time.replace(tzinfo=datetime.timezone.utc)
+ current_time = datetime.datetime.now(datetime.timezone.utc)
+
+ if current_time > expire_time:
+ raise Exception("令牌已过期,请重新登录")
+ else:
+ try:
+ create_tokens(stud_id, pwd)
+ except Exception:
+ raise Exception("令牌有误,请重新登录")
+ return create_tokens(stud_id, pwd)[0]
+
+
+# 验证和读取access_token的stud_id
+def get_stud_id(access_token):
+ try:
+ data = decode_data(access_token)
+ stud_file = CushyOrmCache(get_path())
+ stud = stud_file.query("FileAuth").filter(token=access_token).first()
+ if not stud:
+ raise Exception("无效令牌")
+ except Exception as e:
+ # raise Exception("无效令牌")
+ raise e
+ stud_id = data.split(":")[0]
+ token_time = data.split(":")[2]
+
+ expire_time = datetime.datetime.fromisoformat(token_time)
+ expire_time = expire_time.replace(tzinfo=datetime.timezone.utc)
+ current_time = datetime.datetime.now(datetime.timezone.utc)
+
+ if current_time > expire_time:
+ raise Exception("令牌已过期,请刷新")
+ return stud_id
+ # return data.split(':')[0]
+
+
+# 读取cookie
+def get_cookie(stud_id):
+ stud_file = CushyOrmCache(get_path())
+ stud = stud_file.query("FileAuth").filter(stud_id=stud_id).first()
+ cookies = list_tocookie(stud.cookie)
+ return cookies
diff --git a/ecjtu/ecjtu_api/ecjtu_schema.py b/ecjtu/ecjtu_api/ecjtu_schema.py
deleted file mode 100644
index 8a4406d..0000000
--- a/ecjtu/ecjtu_api/ecjtu_schema.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from pydantic import BaseModel, Field
-
-
-class UserLoginSchema(BaseModel):
- stud_id: str = Field(..., min_length=1)
- password: str = Field(..., min_length=1)
diff --git a/ecjtu/ecjtu_api/middle.py b/ecjtu/ecjtu_api/middle.py
index e69d75a..c97e98a 100644
--- a/ecjtu/ecjtu_api/middle.py
+++ b/ecjtu/ecjtu_api/middle.py
@@ -7,7 +7,7 @@ class MyMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
# 不被拦截的路由
secure_routes = [
- "/token",
+ "/refresh_token",
"/login",
"/",
"/docs",
@@ -24,9 +24,15 @@ async def dispatch(self, request, call_next):
# 是否存在token
if not header:
return response
- token = header
- stud_id, enc_pwd = auth.decode(token)
- if not stud_id or not enc_pwd:
+ # 是否过期
+ try:
+ stud_id = auth.get_stud_id(header)
+ except Exception as e:
+ return respose_result.ResponseResult.auth_error(str(e))
+ if not stud_id:
+ response = respose_result.ResponseResult.auth_error(
+ "access_token令牌已过期,请刷新"
+ )
return response
response = await call_next(request)
diff --git a/ecjtu/ecjtu_api/respose_result.py b/ecjtu/ecjtu_api/respose_result.py
index a3a05c4..0bb3722 100644
--- a/ecjtu/ecjtu_api/respose_result.py
+++ b/ecjtu/ecjtu_api/respose_result.py
@@ -30,6 +30,14 @@ def auth_error(
status_code=401, content={"code": 401, "msg": msg, "data": data}
)
+ @staticmethod
+ def param_error(
+ data: Union[Dict[str, Any], Any] = None, msg: str = "param error"
+ ) -> JSONResponse:
+ return JSONResponse(
+ status_code=400, content={"code": 400, "msg": msg, "data": data}
+ )
+
@staticmethod
def error(
data: Union[Dict[str, Any], Any] = None, msg: str = "error"
diff --git a/ecjtu/ecjtu_api/schema.py b/ecjtu/ecjtu_api/schema.py
new file mode 100644
index 0000000..4063b05
--- /dev/null
+++ b/ecjtu/ecjtu_api/schema.py
@@ -0,0 +1,22 @@
+from typing import Dict, List, Optional
+
+from cushy_storage import BaseORMModel, CushyOrmCache
+from pydantic import BaseModel, Field
+
+
+class UserLoginSchema(BaseModel):
+ stud_id: str = Field(..., min_length=1)
+ password: str = Field(..., min_length=1)
+
+
+class FileAuth(BaseORMModel):
+ def __init__(
+ self,
+ stud_id: Optional[str] = None,
+ token: str = None,
+ cookie: Optional[List[Dict]] = None,
+ ):
+ super().__init__()
+ self.stud_id = stud_id
+ self.token = token
+ self.cookie = cookie
diff --git a/ecjtu/utils/cookie.py b/ecjtu/utils/cookie.py
new file mode 100644
index 0000000..813d095
--- /dev/null
+++ b/ecjtu/utils/cookie.py
@@ -0,0 +1,23 @@
+from typing import Optional
+
+import httpx
+from httpx._types import CookieTypes
+
+
+def cookies_tolist(cookies: Optional[CookieTypes]):
+ cookies_list = []
+ for cookie in cookies.jar:
+ dict = {
+ "name": cookie.name,
+ "value": cookie.value,
+ "domain": cookie.domain,
+ }
+ cookies_list.append(dict)
+ return cookies_list
+
+
+def list_tocookie(cookies_list: list):
+ cookies = httpx.Cookies()
+ for cookie in cookies_list:
+ cookies.set(**cookie)
+ return cookies
diff --git a/ecjtu/utils/logger.py b/ecjtu/utils/logger.py
index e9e00f5..0eb5e68 100644
--- a/ecjtu/utils/logger.py
+++ b/ecjtu/utils/logger.py
@@ -14,6 +14,11 @@ def get_log_path() -> str:
return f"{log_directory}/{current_time}.log"
+def get_path() -> str:
+ log_directory = get_default_storage_path("logs")
+ return f"{log_directory}"
+
+
class LogManager(metaclass=Singleton):
def __init__(self) -> None:
self.logger = logging.getLogger("ecjtu")
diff --git a/poetry.lock b/poetry.lock
index 4528467..9c81838 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -235,13 +235,13 @@ toml = ["tomli"]
[[package]]
name = "coverage-badge"
-version = "1.1.0"
+version = "1.1.1"
description = "Generate coverage badges for Coverage.py."
optional = false
python-versions = "*"
files = [
- {file = "coverage-badge-1.1.0.tar.gz", hash = "sha256:c824a106503e981c02821e7d32f008fb3984b2338aa8c3800ec9357e33345b78"},
- {file = "coverage_badge-1.1.0-py2.py3-none-any.whl", hash = "sha256:e365d56e5202e923d1b237f82defd628a02d1d645a147f867ac85c58c81d7997"},
+ {file = "coverage-badge-1.1.1.tar.gz", hash = "sha256:42252df917404af6147380861228a4ace3d9a29804df8fc2d34a22b2bc4f45b6"},
+ {file = "coverage_badge-1.1.1-py2.py3-none-any.whl", hash = "sha256:1d8e566ad47c37910fa2bbc74ea19972b171b5b4e40624b31b3e2f2d93680266"},
]
[package.dependencies]
@@ -249,43 +249,43 @@ coverage = "*"
[[package]]
name = "cryptography"
-version = "42.0.5"
+version = "42.0.7"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
optional = false
python-versions = ">=3.7"
files = [
- {file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:a30596bae9403a342c978fb47d9b0ee277699fa53bbafad14706af51fe543d16"},
- {file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:b7ffe927ee6531c78f81aa17e684e2ff617daeba7f189f911065b2ea2d526dec"},
- {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2424ff4c4ac7f6b8177b53c17ed5d8fa74ae5955656867f5a8affaca36a27abb"},
- {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:329906dcc7b20ff3cad13c069a78124ed8247adcac44b10bea1130e36caae0b4"},
- {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:b03c2ae5d2f0fc05f9a2c0c997e1bc18c8229f392234e8a0194f202169ccd278"},
- {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f8837fe1d6ac4a8052a9a8ddab256bc006242696f03368a4009be7ee3075cdb7"},
- {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:0270572b8bd2c833c3981724b8ee9747b3ec96f699a9665470018594301439ee"},
- {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:b8cac287fafc4ad485b8a9b67d0ee80c66bf3574f655d3b97ef2e1082360faf1"},
- {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:16a48c23a62a2f4a285699dba2e4ff2d1cff3115b9df052cdd976a18856d8e3d"},
- {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2bce03af1ce5a5567ab89bd90d11e7bbdff56b8af3acbbec1faded8f44cb06da"},
- {file = "cryptography-42.0.5-cp37-abi3-win32.whl", hash = "sha256:b6cd2203306b63e41acdf39aa93b86fb566049aeb6dc489b70e34bcd07adca74"},
- {file = "cryptography-42.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:98d8dc6d012b82287f2c3d26ce1d2dd130ec200c8679b6213b3c73c08b2b7940"},
- {file = "cryptography-42.0.5-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:5e6275c09d2badf57aea3afa80d975444f4be8d3bc58f7f80d2a484c6f9485c8"},
- {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4985a790f921508f36f81831817cbc03b102d643b5fcb81cd33df3fa291a1a1"},
- {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cde5f38e614f55e28d831754e8a3bacf9ace5d1566235e39d91b35502d6936e"},
- {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7367d7b2eca6513681127ebad53b2582911d1736dc2ffc19f2c3ae49997496bc"},
- {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cd2030f6650c089aeb304cf093f3244d34745ce0cfcc39f20c6fbfe030102e2a"},
- {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a2913c5375154b6ef2e91c10b5720ea6e21007412f6437504ffea2109b5a33d7"},
- {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:c41fb5e6a5fe9ebcd58ca3abfeb51dffb5d83d6775405305bfa8715b76521922"},
- {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3eaafe47ec0d0ffcc9349e1708be2aaea4c6dd4978d76bf6eb0cb2c13636c6fc"},
- {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1b95b98b0d2af784078fa69f637135e3c317091b615cd0905f8b8a087e86fa30"},
- {file = "cryptography-42.0.5-cp39-abi3-win32.whl", hash = "sha256:1f71c10d1e88467126f0efd484bd44bca5e14c664ec2ede64c32f20875c0d413"},
- {file = "cryptography-42.0.5-cp39-abi3-win_amd64.whl", hash = "sha256:a011a644f6d7d03736214d38832e030d8268bcff4a41f728e6030325fea3e400"},
- {file = "cryptography-42.0.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9481ffe3cf013b71b2428b905c4f7a9a4f76ec03065b05ff499bb5682a8d9ad8"},
- {file = "cryptography-42.0.5-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:ba334e6e4b1d92442b75ddacc615c5476d4ad55cc29b15d590cc6b86efa487e2"},
- {file = "cryptography-42.0.5-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ba3e4a42397c25b7ff88cdec6e2a16c2be18720f317506ee25210f6d31925f9c"},
- {file = "cryptography-42.0.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:111a0d8553afcf8eb02a4fea6ca4f59d48ddb34497aa8706a6cf536f1a5ec576"},
- {file = "cryptography-42.0.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cd65d75953847815962c84a4654a84850b2bb4aed3f26fadcc1c13892e1e29f6"},
- {file = "cryptography-42.0.5-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e807b3188f9eb0eaa7bbb579b462c5ace579f1cedb28107ce8b48a9f7ad3679e"},
- {file = "cryptography-42.0.5-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f12764b8fffc7a123f641d7d049d382b73f96a34117e0b637b80643169cec8ac"},
- {file = "cryptography-42.0.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:37dd623507659e08be98eec89323469e8c7b4c1407c85112634ae3dbdb926fdd"},
- {file = "cryptography-42.0.5.tar.gz", hash = "sha256:6fe07eec95dfd477eb9530aef5bead34fec819b3aaf6c5bd6d20565da607bfe1"},
+ {file = "cryptography-42.0.7-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:a987f840718078212fdf4504d0fd4c6effe34a7e4740378e59d47696e8dfb477"},
+ {file = "cryptography-42.0.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:bd13b5e9b543532453de08bcdc3cc7cebec6f9883e886fd20a92f26940fd3e7a"},
+ {file = "cryptography-42.0.7-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a79165431551042cc9d1d90e6145d5d0d3ab0f2d66326c201d9b0e7f5bf43604"},
+ {file = "cryptography-42.0.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a47787a5e3649008a1102d3df55424e86606c9bae6fb77ac59afe06d234605f8"},
+ {file = "cryptography-42.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:02c0eee2d7133bdbbc5e24441258d5d2244beb31da5ed19fbb80315f4bbbff55"},
+ {file = "cryptography-42.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:5e44507bf8d14b36b8389b226665d597bc0f18ea035d75b4e53c7b1ea84583cc"},
+ {file = "cryptography-42.0.7-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7f8b25fa616d8b846aef64b15c606bb0828dbc35faf90566eb139aa9cff67af2"},
+ {file = "cryptography-42.0.7-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:93a3209f6bb2b33e725ed08ee0991b92976dfdcf4e8b38646540674fc7508e13"},
+ {file = "cryptography-42.0.7-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e6b8f1881dac458c34778d0a424ae5769de30544fc678eac51c1c8bb2183e9da"},
+ {file = "cryptography-42.0.7-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3de9a45d3b2b7d8088c3fbf1ed4395dfeff79d07842217b38df14ef09ce1d8d7"},
+ {file = "cryptography-42.0.7-cp37-abi3-win32.whl", hash = "sha256:789caea816c6704f63f6241a519bfa347f72fbd67ba28d04636b7c6b7da94b0b"},
+ {file = "cryptography-42.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:8cb8ce7c3347fcf9446f201dc30e2d5a3c898d009126010cbd1f443f28b52678"},
+ {file = "cryptography-42.0.7-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:a3a5ac8b56fe37f3125e5b72b61dcde43283e5370827f5233893d461b7360cd4"},
+ {file = "cryptography-42.0.7-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:779245e13b9a6638df14641d029add5dc17edbef6ec915688f3acb9e720a5858"},
+ {file = "cryptography-42.0.7-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d563795db98b4cd57742a78a288cdbdc9daedac29f2239793071fe114f13785"},
+ {file = "cryptography-42.0.7-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:31adb7d06fe4383226c3e963471f6837742889b3c4caa55aac20ad951bc8ffda"},
+ {file = "cryptography-42.0.7-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:efd0bf5205240182e0f13bcaea41be4fdf5c22c5129fc7ced4a0282ac86998c9"},
+ {file = "cryptography-42.0.7-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a9bc127cdc4ecf87a5ea22a2556cab6c7eda2923f84e4f3cc588e8470ce4e42e"},
+ {file = "cryptography-42.0.7-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:3577d029bc3f4827dd5bf8bf7710cac13527b470bbf1820a3f394adb38ed7d5f"},
+ {file = "cryptography-42.0.7-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2e47577f9b18723fa294b0ea9a17d5e53a227867a0a4904a1a076d1646d45ca1"},
+ {file = "cryptography-42.0.7-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1a58839984d9cb34c855197043eaae2c187d930ca6d644612843b4fe8513c886"},
+ {file = "cryptography-42.0.7-cp39-abi3-win32.whl", hash = "sha256:e6b79d0adb01aae87e8a44c2b64bc3f3fe59515280e00fb6d57a7267a2583cda"},
+ {file = "cryptography-42.0.7-cp39-abi3-win_amd64.whl", hash = "sha256:16268d46086bb8ad5bf0a2b5544d8a9ed87a0e33f5e77dd3c3301e63d941a83b"},
+ {file = "cryptography-42.0.7-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2954fccea107026512b15afb4aa664a5640cd0af630e2ee3962f2602693f0c82"},
+ {file = "cryptography-42.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:362e7197754c231797ec45ee081f3088a27a47c6c01eff2ac83f60f85a50fe60"},
+ {file = "cryptography-42.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4f698edacf9c9e0371112792558d2f705b5645076cc0aaae02f816a0171770fd"},
+ {file = "cryptography-42.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5482e789294854c28237bba77c4c83be698be740e31a3ae5e879ee5444166582"},
+ {file = "cryptography-42.0.7-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e9b2a6309f14c0497f348d08a065d52f3020656f675819fc405fb63bbcd26562"},
+ {file = "cryptography-42.0.7-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d8e3098721b84392ee45af2dd554c947c32cc52f862b6a3ae982dbb90f577f14"},
+ {file = "cryptography-42.0.7-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c65f96dad14f8528a447414125e1fc8feb2ad5a272b8f68477abbcc1ea7d94b9"},
+ {file = "cryptography-42.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:36017400817987670037fbb0324d71489b6ead6231c9604f8fc1f7d008087c68"},
+ {file = "cryptography-42.0.7.tar.gz", hash = "sha256:ecbfbc00bf55888edda9868a4cf927205de8499e7fabe6c050322298382953f2"},
]
[package.dependencies]
@@ -301,6 +301,17 @@ ssh = ["bcrypt (>=3.1.5)"]
test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"]
test-randomorder = ["pytest-randomly"]
+[[package]]
+name = "cushy-storage"
+version = "1.3.8"
+description = "A data local persistence ORM framework."
+optional = false
+python-versions = "<4.0,>=3.8"
+files = [
+ {file = "cushy_storage-1.3.8-py3-none-any.whl", hash = "sha256:683f58fea2d05897f02ac24e80b4aff06ab45238e254ecbe933bc5d459c5b53f"},
+ {file = "cushy_storage-1.3.8.tar.gz", hash = "sha256:82e5af7329da08d0190d26f4cc9a217eac74c94754570e80429dedf6421c48db"},
+]
+
[[package]]
name = "distlib"
version = "0.3.8"
@@ -406,13 +417,13 @@ uvicorn = {version = ">=0.29.0", extras = ["standard"]}
[[package]]
name = "filelock"
-version = "3.13.4"
+version = "3.14.0"
description = "A platform independent file lock."
optional = false
python-versions = ">=3.8"
files = [
- {file = "filelock-3.13.4-py3-none-any.whl", hash = "sha256:404e5e9253aa60ad457cae1be07c0f0ca90a63931200a47d9b6a6af84fd7b45f"},
- {file = "filelock-3.13.4.tar.gz", hash = "sha256:d13f466618bfde72bd2c18255e269f72542c6e70e7bac83a0232d6b1cc5c8cf4"},
+ {file = "filelock-3.14.0-py3-none-any.whl", hash = "sha256:43339835842f110ca7ae60f1e1c160714c5a6afd15a2873419ab185334975c0f"},
+ {file = "filelock-3.14.0.tar.gz", hash = "sha256:6ea72da3be9b8c82afd3edcf99f2fffbb5076335a5ae4d03248bb5b6c3eae78a"},
]
[package.extras]
@@ -526,13 +537,13 @@ socks = ["socksio (==1.*)"]
[[package]]
name = "identify"
-version = "2.5.35"
+version = "2.5.36"
description = "File identification library for Python"
optional = false
python-versions = ">=3.8"
files = [
- {file = "identify-2.5.35-py2.py3-none-any.whl", hash = "sha256:c4de0081837b211594f8e877a6b4fad7ca32bbfc1a9307fdd61c28bfe923f13e"},
- {file = "identify-2.5.35.tar.gz", hash = "sha256:10a7ca245cfcd756a554a7288159f72ff105ad233c7c4b9c6f0f4d108f5f6791"},
+ {file = "identify-2.5.36-py2.py3-none-any.whl", hash = "sha256:37d93f380f4de590500d9dba7db359d0d3da95ffe7f9de1753faa159e71e7dfa"},
+ {file = "identify-2.5.36.tar.gz", hash = "sha256:e5e00f54165f9047fbebeb4a560f9acfb8af4c88232be60a488e9b68d122745d"},
]
[package.extras]
@@ -763,28 +774,29 @@ files = [
[[package]]
name = "platformdirs"
-version = "4.2.0"
-description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
+version = "4.2.1"
+description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
optional = false
python-versions = ">=3.8"
files = [
- {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"},
- {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"},
+ {file = "platformdirs-4.2.1-py3-none-any.whl", hash = "sha256:17d5a1161b3fd67b390023cb2d3b026bbd40abde6fdb052dfbd3a29c3ba22ee1"},
+ {file = "platformdirs-4.2.1.tar.gz", hash = "sha256:031cd18d4ec63ec53e82dceaac0417d218a6863f7745dfcc9efe7793b7039bdf"},
]
[package.extras]
docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"]
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"]
+type = ["mypy (>=1.8)"]
[[package]]
name = "pluggy"
-version = "1.4.0"
+version = "1.5.0"
description = "plugin and hook calling mechanisms for python"
optional = false
python-versions = ">=3.8"
files = [
- {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"},
- {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"},
+ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
+ {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
]
[package.extras]
@@ -833,18 +845,18 @@ files = [
[[package]]
name = "pydantic"
-version = "2.7.0"
+version = "2.7.1"
description = "Data validation using Python type hints"
optional = false
python-versions = ">=3.8"
files = [
- {file = "pydantic-2.7.0-py3-none-any.whl", hash = "sha256:9dee74a271705f14f9a1567671d144a851c675b072736f0a7b2608fd9e495352"},
- {file = "pydantic-2.7.0.tar.gz", hash = "sha256:b5ecdd42262ca2462e2624793551e80911a1e989f462910bb81aef974b4bb383"},
+ {file = "pydantic-2.7.1-py3-none-any.whl", hash = "sha256:e029badca45266732a9a79898a15ae2e8b14840b1eabbb25844be28f0b33f3d5"},
+ {file = "pydantic-2.7.1.tar.gz", hash = "sha256:e9dbb5eada8abe4d9ae5f46b9939aead650cd2b68f249bb3a8139dbe125803cc"},
]
[package.dependencies]
annotated-types = ">=0.4.0"
-pydantic-core = "2.18.1"
+pydantic-core = "2.18.2"
typing-extensions = ">=4.6.1"
[package.extras]
@@ -852,90 +864,90 @@ email = ["email-validator (>=2.0.0)"]
[[package]]
name = "pydantic-core"
-version = "2.18.1"
+version = "2.18.2"
description = "Core functionality for Pydantic validation and serialization"
optional = false
python-versions = ">=3.8"
files = [
- {file = "pydantic_core-2.18.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:ee9cf33e7fe14243f5ca6977658eb7d1042caaa66847daacbd2117adb258b226"},
- {file = "pydantic_core-2.18.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6b7bbb97d82659ac8b37450c60ff2e9f97e4eb0f8a8a3645a5568b9334b08b50"},
- {file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df4249b579e75094f7e9bb4bd28231acf55e308bf686b952f43100a5a0be394c"},
- {file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d0491006a6ad20507aec2be72e7831a42efc93193d2402018007ff827dc62926"},
- {file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ae80f72bb7a3e397ab37b53a2b49c62cc5496412e71bc4f1277620a7ce3f52b"},
- {file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:58aca931bef83217fca7a390e0486ae327c4af9c3e941adb75f8772f8eeb03a1"},
- {file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1be91ad664fc9245404a789d60cba1e91c26b1454ba136d2a1bf0c2ac0c0505a"},
- {file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:667880321e916a8920ef49f5d50e7983792cf59f3b6079f3c9dac2b88a311d17"},
- {file = "pydantic_core-2.18.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f7054fdc556f5421f01e39cbb767d5ec5c1139ea98c3e5b350e02e62201740c7"},
- {file = "pydantic_core-2.18.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:030e4f9516f9947f38179249778709a460a3adb516bf39b5eb9066fcfe43d0e6"},
- {file = "pydantic_core-2.18.1-cp310-none-win32.whl", hash = "sha256:2e91711e36e229978d92642bfc3546333a9127ecebb3f2761372e096395fc649"},
- {file = "pydantic_core-2.18.1-cp310-none-win_amd64.whl", hash = "sha256:9a29726f91c6cb390b3c2338f0df5cd3e216ad7a938762d11c994bb37552edb0"},
- {file = "pydantic_core-2.18.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:9ece8a49696669d483d206b4474c367852c44815fca23ac4e48b72b339807f80"},
- {file = "pydantic_core-2.18.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a5d83efc109ceddb99abd2c1316298ced2adb4570410defe766851a804fcd5b"},
- {file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f7973c381283783cd1043a8c8f61ea5ce7a3a58b0369f0ee0ee975eaf2f2a1b"},
- {file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:54c7375c62190a7845091f521add19b0f026bcf6ae674bdb89f296972272e86d"},
- {file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dd63cec4e26e790b70544ae5cc48d11b515b09e05fdd5eff12e3195f54b8a586"},
- {file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:561cf62c8a3498406495cfc49eee086ed2bb186d08bcc65812b75fda42c38294"},
- {file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68717c38a68e37af87c4da20e08f3e27d7e4212e99e96c3d875fbf3f4812abfc"},
- {file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d5728e93d28a3c63ee513d9ffbac9c5989de8c76e049dbcb5bfe4b923a9739d"},
- {file = "pydantic_core-2.18.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f0f17814c505f07806e22b28856c59ac80cee7dd0fbb152aed273e116378f519"},
- {file = "pydantic_core-2.18.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d816f44a51ba5175394bc6c7879ca0bd2be560b2c9e9f3411ef3a4cbe644c2e9"},
- {file = "pydantic_core-2.18.1-cp311-none-win32.whl", hash = "sha256:09f03dfc0ef8c22622eaa8608caa4a1e189cfb83ce847045eca34f690895eccb"},
- {file = "pydantic_core-2.18.1-cp311-none-win_amd64.whl", hash = "sha256:27f1009dc292f3b7ca77feb3571c537276b9aad5dd4efb471ac88a8bd09024e9"},
- {file = "pydantic_core-2.18.1-cp311-none-win_arm64.whl", hash = "sha256:48dd883db92e92519201f2b01cafa881e5f7125666141a49ffba8b9facc072b0"},
- {file = "pydantic_core-2.18.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b6b0e4912030c6f28bcb72b9ebe4989d6dc2eebcd2a9cdc35fefc38052dd4fe8"},
- {file = "pydantic_core-2.18.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3202a429fe825b699c57892d4371c74cc3456d8d71b7f35d6028c96dfecad31"},
- {file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3982b0a32d0a88b3907e4b0dc36809fda477f0757c59a505d4e9b455f384b8b"},
- {file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25595ac311f20e5324d1941909b0d12933f1fd2171075fcff763e90f43e92a0d"},
- {file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:14fe73881cf8e4cbdaded8ca0aa671635b597e42447fec7060d0868b52d074e6"},
- {file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca976884ce34070799e4dfc6fbd68cb1d181db1eefe4a3a94798ddfb34b8867f"},
- {file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:684d840d2c9ec5de9cb397fcb3f36d5ebb6fa0d94734f9886032dd796c1ead06"},
- {file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:54764c083bbe0264f0f746cefcded6cb08fbbaaf1ad1d78fb8a4c30cff999a90"},
- {file = "pydantic_core-2.18.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:201713f2f462e5c015b343e86e68bd8a530a4f76609b33d8f0ec65d2b921712a"},
- {file = "pydantic_core-2.18.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fd1a9edb9dd9d79fbeac1ea1f9a8dd527a6113b18d2e9bcc0d541d308dae639b"},
- {file = "pydantic_core-2.18.1-cp312-none-win32.whl", hash = "sha256:d5e6b7155b8197b329dc787356cfd2684c9d6a6b1a197f6bbf45f5555a98d411"},
- {file = "pydantic_core-2.18.1-cp312-none-win_amd64.whl", hash = "sha256:9376d83d686ec62e8b19c0ac3bf8d28d8a5981d0df290196fb6ef24d8a26f0d6"},
- {file = "pydantic_core-2.18.1-cp312-none-win_arm64.whl", hash = "sha256:c562b49c96906b4029b5685075fe1ebd3b5cc2601dfa0b9e16c2c09d6cbce048"},
- {file = "pydantic_core-2.18.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:3e352f0191d99fe617371096845070dee295444979efb8f27ad941227de6ad09"},
- {file = "pydantic_core-2.18.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c0295d52b012cbe0d3059b1dba99159c3be55e632aae1999ab74ae2bd86a33d7"},
- {file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56823a92075780582d1ffd4489a2e61d56fd3ebb4b40b713d63f96dd92d28144"},
- {file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dd3f79e17b56741b5177bcc36307750d50ea0698df6aa82f69c7db32d968c1c2"},
- {file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38a5024de321d672a132b1834a66eeb7931959c59964b777e8f32dbe9523f6b1"},
- {file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d2ce426ee691319d4767748c8e0895cfc56593d725594e415f274059bcf3cb76"},
- {file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2adaeea59849ec0939af5c5d476935f2bab4b7f0335b0110f0f069a41024278e"},
- {file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9b6431559676a1079eac0f52d6d0721fb8e3c5ba43c37bc537c8c83724031feb"},
- {file = "pydantic_core-2.18.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:85233abb44bc18d16e72dc05bf13848a36f363f83757541f1a97db2f8d58cfd9"},
- {file = "pydantic_core-2.18.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:641a018af4fe48be57a2b3d7a1f0f5dbca07c1d00951d3d7463f0ac9dac66622"},
- {file = "pydantic_core-2.18.1-cp38-none-win32.whl", hash = "sha256:63d7523cd95d2fde0d28dc42968ac731b5bb1e516cc56b93a50ab293f4daeaad"},
- {file = "pydantic_core-2.18.1-cp38-none-win_amd64.whl", hash = "sha256:907a4d7720abfcb1c81619863efd47c8a85d26a257a2dbebdb87c3b847df0278"},
- {file = "pydantic_core-2.18.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:aad17e462f42ddbef5984d70c40bfc4146c322a2da79715932cd8976317054de"},
- {file = "pydantic_core-2.18.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:94b9769ba435b598b547c762184bcfc4783d0d4c7771b04a3b45775c3589ca44"},
- {file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80e0e57cc704a52fb1b48f16d5b2c8818da087dbee6f98d9bf19546930dc64b5"},
- {file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:76b86e24039c35280ceee6dce7e62945eb93a5175d43689ba98360ab31eebc4a"},
- {file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12a05db5013ec0ca4a32cc6433f53faa2a014ec364031408540ba858c2172bb0"},
- {file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:250ae39445cb5475e483a36b1061af1bc233de3e9ad0f4f76a71b66231b07f88"},
- {file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a32204489259786a923e02990249c65b0f17235073149d0033efcebe80095570"},
- {file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6395a4435fa26519fd96fdccb77e9d00ddae9dd6c742309bd0b5610609ad7fb2"},
- {file = "pydantic_core-2.18.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2533ad2883f001efa72f3d0e733fb846710c3af6dcdd544fe5bf14fa5fe2d7db"},
- {file = "pydantic_core-2.18.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b560b72ed4816aee52783c66854d96157fd8175631f01ef58e894cc57c84f0f6"},
- {file = "pydantic_core-2.18.1-cp39-none-win32.whl", hash = "sha256:582cf2cead97c9e382a7f4d3b744cf0ef1a6e815e44d3aa81af3ad98762f5a9b"},
- {file = "pydantic_core-2.18.1-cp39-none-win_amd64.whl", hash = "sha256:ca71d501629d1fa50ea7fa3b08ba884fe10cefc559f5c6c8dfe9036c16e8ae89"},
- {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e178e5b66a06ec5bf51668ec0d4ac8cfb2bdcb553b2c207d58148340efd00143"},
- {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:72722ce529a76a4637a60be18bd789d8fb871e84472490ed7ddff62d5fed620d"},
- {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fe0c1ce5b129455e43f941f7a46f61f3d3861e571f2905d55cdbb8b5c6f5e2c"},
- {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4284c621f06a72ce2cb55f74ea3150113d926a6eb78ab38340c08f770eb9b4d"},
- {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a0c3e718f4e064efde68092d9d974e39572c14e56726ecfaeebbe6544521f47"},
- {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:2027493cc44c23b598cfaf200936110433d9caa84e2c6cf487a83999638a96ac"},
- {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:76909849d1a6bffa5a07742294f3fa1d357dc917cb1fe7b470afbc3a7579d539"},
- {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ee7ccc7fb7e921d767f853b47814c3048c7de536663e82fbc37f5eb0d532224b"},
- {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ee2794111c188548a4547eccc73a6a8527fe2af6cf25e1a4ebda2fd01cdd2e60"},
- {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a139fe9f298dc097349fb4f28c8b81cc7a202dbfba66af0e14be5cfca4ef7ce5"},
- {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d074b07a10c391fc5bbdcb37b2f16f20fcd9e51e10d01652ab298c0d07908ee2"},
- {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c69567ddbac186e8c0aadc1f324a60a564cfe25e43ef2ce81bcc4b8c3abffbae"},
- {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:baf1c7b78cddb5af00971ad5294a4583188bda1495b13760d9f03c9483bb6203"},
- {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:2684a94fdfd1b146ff10689c6e4e815f6a01141781c493b97342cdc5b06f4d5d"},
- {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:73c1bc8a86a5c9e8721a088df234265317692d0b5cd9e86e975ce3bc3db62a59"},
- {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e60defc3c15defb70bb38dd605ff7e0fae5f6c9c7cbfe0ad7868582cb7e844a6"},
- {file = "pydantic_core-2.18.1.tar.gz", hash = "sha256:de9d3e8717560eb05e28739d1b35e4eac2e458553a52a301e51352a7ffc86a35"},
+ {file = "pydantic_core-2.18.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:9e08e867b306f525802df7cd16c44ff5ebbe747ff0ca6cf3fde7f36c05a59a81"},
+ {file = "pydantic_core-2.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f0a21cbaa69900cbe1a2e7cad2aa74ac3cf21b10c3efb0fa0b80305274c0e8a2"},
+ {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0680b1f1f11fda801397de52c36ce38ef1c1dc841a0927a94f226dea29c3ae3d"},
+ {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:95b9d5e72481d3780ba3442eac863eae92ae43a5f3adb5b4d0a1de89d42bb250"},
+ {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fcf5cd9c4b655ad666ca332b9a081112cd7a58a8b5a6ca7a3104bc950f2038"},
+ {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b5155ff768083cb1d62f3e143b49a8a3432e6789a3abee8acd005c3c7af1c74"},
+ {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:553ef617b6836fc7e4df130bb851e32fe357ce36336d897fd6646d6058d980af"},
+ {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b89ed9eb7d616ef5714e5590e6cf7f23b02d0d539767d33561e3675d6f9e3857"},
+ {file = "pydantic_core-2.18.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:75f7e9488238e920ab6204399ded280dc4c307d034f3924cd7f90a38b1829563"},
+ {file = "pydantic_core-2.18.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ef26c9e94a8c04a1b2924149a9cb081836913818e55681722d7f29af88fe7b38"},
+ {file = "pydantic_core-2.18.2-cp310-none-win32.whl", hash = "sha256:182245ff6b0039e82b6bb585ed55a64d7c81c560715d1bad0cbad6dfa07b4027"},
+ {file = "pydantic_core-2.18.2-cp310-none-win_amd64.whl", hash = "sha256:e23ec367a948b6d812301afc1b13f8094ab7b2c280af66ef450efc357d2ae543"},
+ {file = "pydantic_core-2.18.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:219da3f096d50a157f33645a1cf31c0ad1fe829a92181dd1311022f986e5fbe3"},
+ {file = "pydantic_core-2.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cc1cfd88a64e012b74e94cd00bbe0f9c6df57049c97f02bb07d39e9c852e19a4"},
+ {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05b7133a6e6aeb8df37d6f413f7705a37ab4031597f64ab56384c94d98fa0e90"},
+ {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:224c421235f6102e8737032483f43c1a8cfb1d2f45740c44166219599358c2cd"},
+ {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b14d82cdb934e99dda6d9d60dc84a24379820176cc4a0d123f88df319ae9c150"},
+ {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2728b01246a3bba6de144f9e3115b532ee44bd6cf39795194fb75491824a1413"},
+ {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:470b94480bb5ee929f5acba6995251ada5e059a5ef3e0dfc63cca287283ebfa6"},
+ {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:997abc4df705d1295a42f95b4eec4950a37ad8ae46d913caeee117b6b198811c"},
+ {file = "pydantic_core-2.18.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75250dbc5290e3f1a0f4618db35e51a165186f9034eff158f3d490b3fed9f8a0"},
+ {file = "pydantic_core-2.18.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4456f2dca97c425231d7315737d45239b2b51a50dc2b6f0c2bb181fce6207664"},
+ {file = "pydantic_core-2.18.2-cp311-none-win32.whl", hash = "sha256:269322dcc3d8bdb69f054681edff86276b2ff972447863cf34c8b860f5188e2e"},
+ {file = "pydantic_core-2.18.2-cp311-none-win_amd64.whl", hash = "sha256:800d60565aec896f25bc3cfa56d2277d52d5182af08162f7954f938c06dc4ee3"},
+ {file = "pydantic_core-2.18.2-cp311-none-win_arm64.whl", hash = "sha256:1404c69d6a676245199767ba4f633cce5f4ad4181f9d0ccb0577e1f66cf4c46d"},
+ {file = "pydantic_core-2.18.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:fb2bd7be70c0fe4dfd32c951bc813d9fe6ebcbfdd15a07527796c8204bd36242"},
+ {file = "pydantic_core-2.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6132dd3bd52838acddca05a72aafb6eab6536aa145e923bb50f45e78b7251043"},
+ {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d904828195733c183d20a54230c0df0eb46ec746ea1a666730787353e87182"},
+ {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c9bd70772c720142be1020eac55f8143a34ec9f82d75a8e7a07852023e46617f"},
+ {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b8ed04b3582771764538f7ee7001b02e1170223cf9b75dff0bc698fadb00cf3"},
+ {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e6dac87ddb34aaec85f873d737e9d06a3555a1cc1a8e0c44b7f8d5daeb89d86f"},
+ {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ca4ae5a27ad7a4ee5170aebce1574b375de390bc01284f87b18d43a3984df72"},
+ {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:886eec03591b7cf058467a70a87733b35f44707bd86cf64a615584fd72488b7c"},
+ {file = "pydantic_core-2.18.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ca7b0c1f1c983e064caa85f3792dd2fe3526b3505378874afa84baf662e12241"},
+ {file = "pydantic_core-2.18.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b4356d3538c3649337df4074e81b85f0616b79731fe22dd11b99499b2ebbdf3"},
+ {file = "pydantic_core-2.18.2-cp312-none-win32.whl", hash = "sha256:8b172601454f2d7701121bbec3425dd71efcb787a027edf49724c9cefc14c038"},
+ {file = "pydantic_core-2.18.2-cp312-none-win_amd64.whl", hash = "sha256:b1bd7e47b1558ea872bd16c8502c414f9e90dcf12f1395129d7bb42a09a95438"},
+ {file = "pydantic_core-2.18.2-cp312-none-win_arm64.whl", hash = "sha256:98758d627ff397e752bc339272c14c98199c613f922d4a384ddc07526c86a2ec"},
+ {file = "pydantic_core-2.18.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:9fdad8e35f278b2c3eb77cbdc5c0a49dada440657bf738d6905ce106dc1de439"},
+ {file = "pydantic_core-2.18.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1d90c3265ae107f91a4f279f4d6f6f1d4907ac76c6868b27dc7fb33688cfb347"},
+ {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:390193c770399861d8df9670fb0d1874f330c79caaca4642332df7c682bf6b91"},
+ {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:82d5d4d78e4448683cb467897fe24e2b74bb7b973a541ea1dcfec1d3cbce39fb"},
+ {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4774f3184d2ef3e14e8693194f661dea5a4d6ca4e3dc8e39786d33a94865cefd"},
+ {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4d938ec0adf5167cb335acb25a4ee69a8107e4984f8fbd2e897021d9e4ca21b"},
+ {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0e8b1be28239fc64a88a8189d1df7fad8be8c1ae47fcc33e43d4be15f99cc70"},
+ {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:868649da93e5a3d5eacc2b5b3b9235c98ccdbfd443832f31e075f54419e1b96b"},
+ {file = "pydantic_core-2.18.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:78363590ef93d5d226ba21a90a03ea89a20738ee5b7da83d771d283fd8a56761"},
+ {file = "pydantic_core-2.18.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:852e966fbd035a6468fc0a3496589b45e2208ec7ca95c26470a54daed82a0788"},
+ {file = "pydantic_core-2.18.2-cp38-none-win32.whl", hash = "sha256:6a46e22a707e7ad4484ac9ee9f290f9d501df45954184e23fc29408dfad61350"},
+ {file = "pydantic_core-2.18.2-cp38-none-win_amd64.whl", hash = "sha256:d91cb5ea8b11607cc757675051f61b3d93f15eca3cefb3e6c704a5d6e8440f4e"},
+ {file = "pydantic_core-2.18.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:ae0a8a797a5e56c053610fa7be147993fe50960fa43609ff2a9552b0e07013e8"},
+ {file = "pydantic_core-2.18.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:042473b6280246b1dbf530559246f6842b56119c2926d1e52b631bdc46075f2a"},
+ {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a388a77e629b9ec814c1b1e6b3b595fe521d2cdc625fcca26fbc2d44c816804"},
+ {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25add29b8f3b233ae90ccef2d902d0ae0432eb0d45370fe315d1a5cf231004b"},
+ {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f459a5ce8434614dfd39bbebf1041952ae01da6bed9855008cb33b875cb024c0"},
+ {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eff2de745698eb46eeb51193a9f41d67d834d50e424aef27df2fcdee1b153845"},
+ {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8309f67285bdfe65c372ea3722b7a5642680f3dba538566340a9d36e920b5f0"},
+ {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f93a8a2e3938ff656a7c1bc57193b1319960ac015b6e87d76c76bf14fe0244b4"},
+ {file = "pydantic_core-2.18.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:22057013c8c1e272eb8d0eebc796701167d8377441ec894a8fed1af64a0bf399"},
+ {file = "pydantic_core-2.18.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cfeecd1ac6cc1fb2692c3d5110781c965aabd4ec5d32799773ca7b1456ac636b"},
+ {file = "pydantic_core-2.18.2-cp39-none-win32.whl", hash = "sha256:0d69b4c2f6bb3e130dba60d34c0845ba31b69babdd3f78f7c0c8fae5021a253e"},
+ {file = "pydantic_core-2.18.2-cp39-none-win_amd64.whl", hash = "sha256:d9319e499827271b09b4e411905b24a426b8fb69464dfa1696258f53a3334641"},
+ {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a1874c6dd4113308bd0eb568418e6114b252afe44319ead2b4081e9b9521fe75"},
+ {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:ccdd111c03bfd3666bd2472b674c6899550e09e9f298954cfc896ab92b5b0e6d"},
+ {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e18609ceaa6eed63753037fc06ebb16041d17d28199ae5aba0052c51449650a9"},
+ {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e5c584d357c4e2baf0ff7baf44f4994be121e16a2c88918a5817331fc7599d7"},
+ {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43f0f463cf89ace478de71a318b1b4f05ebc456a9b9300d027b4b57c1a2064fb"},
+ {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e1b395e58b10b73b07b7cf740d728dd4ff9365ac46c18751bf8b3d8cca8f625a"},
+ {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0098300eebb1c837271d3d1a2cd2911e7c11b396eac9661655ee524a7f10587b"},
+ {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:36789b70d613fbac0a25bb07ab3d9dba4d2e38af609c020cf4d888d165ee0bf3"},
+ {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3f9a801e7c8f1ef8718da265bba008fa121243dfe37c1cea17840b0944dfd72c"},
+ {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:3a6515ebc6e69d85502b4951d89131ca4e036078ea35533bb76327f8424531ce"},
+ {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20aca1e2298c56ececfd8ed159ae4dde2df0781988c97ef77d5c16ff4bd5b400"},
+ {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:223ee893d77a310a0391dca6df00f70bbc2f36a71a895cecd9a0e762dc37b349"},
+ {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2334ce8c673ee93a1d6a65bd90327588387ba073c17e61bf19b4fd97d688d63c"},
+ {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:cbca948f2d14b09d20268cda7b0367723d79063f26c4ffc523af9042cad95592"},
+ {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b3ef08e20ec49e02d5c6717a91bb5af9b20f1805583cb0adfe9ba2c6b505b5ae"},
+ {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6fdc8627910eed0c01aed6a390a252fe3ea6d472ee70fdde56273f198938374"},
+ {file = "pydantic_core-2.18.2.tar.gz", hash = "sha256:2e29d20810dfc3043ee13ac7d9e25105799817683348823f305ab3f349b9386e"},
]
[package.dependencies]
@@ -1447,13 +1459,13 @@ test = ["Cython (>=0.29.36,<0.30.0)", "aiohttp (==3.9.0b0)", "aiohttp (>=3.8.1)"
[[package]]
name = "virtualenv"
-version = "20.25.3"
+version = "20.26.1"
description = "Virtual Python Environment builder"
optional = false
python-versions = ">=3.7"
files = [
- {file = "virtualenv-20.25.3-py3-none-any.whl", hash = "sha256:8aac4332f2ea6ef519c648d0bc48a5b1d324994753519919bddbb1aff25a104e"},
- {file = "virtualenv-20.25.3.tar.gz", hash = "sha256:7bb554bbdfeaacc3349fa614ea5bff6ac300fc7c335e9facf3a3bcfc703f45be"},
+ {file = "virtualenv-20.26.1-py3-none-any.whl", hash = "sha256:7aa9982a728ae5892558bff6a2839c00b9ed145523ece2274fad6f414690ae75"},
+ {file = "virtualenv-20.26.1.tar.gz", hash = "sha256:604bfdceaeece392802e6ae48e69cec49168b9c5f4a44e483963f9242eb0e78b"},
]
[package.dependencies]
@@ -1636,4 +1648,4 @@ files = [
[metadata]
lock-version = "2.0"
python-versions = ">=3.8"
-content-hash = "e17c06bc0dfb64dc90ed83f7fd1bee2f74204b8bcc5539a6e3a0cac6b8f90b3c"
+content-hash = "1e064562d8c36bdbb4b2aa22c9c80e10e8f447e647e172363141e0cec4bc0de6"
diff --git a/pyproject.toml b/pyproject.toml
index 024b4b1..913ac4e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -46,6 +46,9 @@ pre-commit = "^2.21.0"
coverage = "^6.1.2"
coverage-badge = "^1.1.0"
+[tool.poetry.dependencies.cushy-storage]
+version = "1.3.8"
+python = "<4.0,>=3.8"
[tool.ruff]
# https://beta.ruff.rs/docs/settings/
From 674734325aaa9a553adc4027b58ae70750b99228 Mon Sep 17 00:00:00 2001
From: longsihua2003 <1161443436@qq.com>
Date: Thu, 9 May 2024 00:21:02 +0800
Subject: [PATCH 5/6] Add web apis, refine code comments, add readme
information
---
README.md | 62 ++++++++++++++++++++-
ecjtu/client.py | 8 ---
ecjtu/ecjtu_api/api.py | 2 +-
ecjtu/ecjtu_api/auth.py | 19 +++----
ecjtu/ecjtu_api/schema.py | 2 +-
ecjtu/utils/cookie.py | 8 +--
examples/ecjtu-api.md | 71 ++++++++++++++++++++----
examples/ecjtu.api.ipynb | 114 --------------------------------------
8 files changed, 135 insertions(+), 151 deletions(-)
delete mode 100644 examples/ecjtu.api.ipynb
diff --git a/README.md b/README.md
index c3f53e8..644f58e 100644
--- a/README.md
+++ b/README.md
@@ -261,6 +261,67 @@ async def main():
asyncio.run(main())
```
+## 提供 web 服务器,提供 API 服务
+
+### 启动方法
+1. 通过python代码启动
+ ```py
+ from ecjtu.ecjtu_api.api import start_api_server
+
+ def main():
+ start_api_server(port=8080)
+
+ if __name__ == "__main__":
+ main()
+ ```
+2. 通过命令行启动
+ ```shell
+ python ecjtu/server.py --port 8080
+ ```
+
+### 使用方法
+1. 启动之后,命令行会显示如下内容
+ ```shell
+ INFO: Started server process [2545]
+ INFO: Waiting for application startup.
+ INFO: Application startup complete.
+ INFO: Uvicorn running on http://127.0.0.1:8080 (Press CTRL+C to quit)
+ ```
+2. 此时通过浏览器访问 http://127.0.0.1:8080 可以看到api在线调试文档
+
+### 本项目提供的api接口
+
+详细信息可以参考源代码当中examples/ecjtu-api.md当中
+
+1. 登录
+ * post /login
+ 通过学号和密码进行登录,获取access_token和refresh_token,access_token用于之后的所有请求,refresh_token用于刷新access_token
+
+ * post /refresh_token
+ 当access_token过期时,可以使用refresh_token刷新access_token。
+2. gpa
+
+ * get /gpa
+ 获取当前gpa情况
+3. 课表
+ * get /schedule
+ 获取今日课表
+ * get /schedule/{date}
+ 获取指定日期课表 date格式为2024-05-01
+ * get /schedule/week
+ 获取本周课表
+4. 成绩
+ * get /score
+ 获取目前成绩
+ * /score/{semester}
+ 获取指定学期成绩 semester格式为2023.1
+5. 选课情况
+ * get /elective_courses
+ 获取当前选课信息
+ * get /elective_courses/{semester}
+ 获取指定学期选课信息 semester格式为2023.1
+
+
## 🧰 本地开发
欢迎贡献代码与二次开发,你可以通过以下方式安装依赖,推荐使用 Conda 作为环境管理工具,首先创建一个新的环境并激活:
@@ -281,7 +342,6 @@ poetry install
下面列举了一些未来可能添加的功能,欢迎贡献代码,提出建议。
-- [ ] 添加 web 服务器,提供 API 服务
- [ ] 提供 vercel 一键部署
- [ ] 提供 docker 快速服务部署
- [ ] 增加考试查询
diff --git a/ecjtu/client.py b/ecjtu/client.py
index 71fff40..f380b66 100644
--- a/ecjtu/client.py
+++ b/ecjtu/client.py
@@ -224,10 +224,6 @@ def login(self) -> None:
logger.info("Login successful")
- def start_api_server(self, port: int = 8000):
- # TODO: Start a FastAPI server
- pass
-
class AsyncECJTU(BaseClient[httpx.AsyncClient], httpx.AsyncClient):
def __init__(self, stud_id: str, password: str, **kwargs) -> None:
@@ -384,7 +380,3 @@ async def login(self) -> None:
)
logger.info("Login successful")
-
- async def start_api_server(self):
- # TODO: Start a FastAPI server
- pass
diff --git a/ecjtu/ecjtu_api/api.py b/ecjtu/ecjtu_api/api.py
index 6e9b5cf..f91ba9a 100644
--- a/ecjtu/ecjtu_api/api.py
+++ b/ecjtu/ecjtu_api/api.py
@@ -23,7 +23,7 @@ def push_docs():
"/login",
tags=["登录"],
summary="登录",
- description="登录并获取token,以下所有接口都需要token才可以使用",
+ description="登录获取access_token和refresh_token,access_token用于之后的所有请求,refresh_token用于刷新access_token",
)
def login(user: schema.UserLoginSchema):
try:
diff --git a/ecjtu/ecjtu_api/auth.py b/ecjtu/ecjtu_api/auth.py
index 193e5d3..db3f1c3 100644
--- a/ecjtu/ecjtu_api/auth.py
+++ b/ecjtu/ecjtu_api/auth.py
@@ -1,6 +1,7 @@
import base64
import datetime
+import httpx
from cushy_storage import CushyOrmCache
from ecjtu.client import ECJTU
@@ -10,18 +11,18 @@
from . import schema
-def encode_data(data):
+def encode_data(data: str) -> str:
# 将数据编码为base64字符串
return base64.b64encode(data.encode()).decode()
-def decode_data(encoded_data):
+def decode_data(encoded_data: str) -> str:
# 解码base64字符串
return base64.b64decode(encoded_data.encode()).decode()
# 双token加密
-def create_tokens(stud_id, pwd):
+def create_tokens(stud_id: str, pwd: str) -> tuple[str, str]:
access_time = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(
minutes=60
)
@@ -56,7 +57,7 @@ def create_tokens(stud_id, pwd):
# 刷新access_token
-def refresh_access_token(refresh_token):
+def refresh_access_token(refresh_token: str) -> str:
data = decode_data(refresh_token)
print(data)
stud_id = data.split(":")[0]
@@ -78,16 +79,15 @@ def refresh_access_token(refresh_token):
# 验证和读取access_token的stud_id
-def get_stud_id(access_token):
+def get_stud_id(access_token: str) -> str:
try:
data = decode_data(access_token)
stud_file = CushyOrmCache(get_path())
stud = stud_file.query("FileAuth").filter(token=access_token).first()
if not stud:
raise Exception("无效令牌")
- except Exception as e:
- # raise Exception("无效令牌")
- raise e
+ except Exception:
+ raise Exception("无效令牌")
stud_id = data.split(":")[0]
token_time = data.split(":")[2]
@@ -98,11 +98,10 @@ def get_stud_id(access_token):
if current_time > expire_time:
raise Exception("令牌已过期,请刷新")
return stud_id
- # return data.split(':')[0]
# 读取cookie
-def get_cookie(stud_id):
+def get_cookie(stud_id: str) -> httpx.Cookies:
stud_file = CushyOrmCache(get_path())
stud = stud_file.query("FileAuth").filter(stud_id=stud_id).first()
cookies = list_tocookie(stud.cookie)
diff --git a/ecjtu/ecjtu_api/schema.py b/ecjtu/ecjtu_api/schema.py
index 4063b05..82fc442 100644
--- a/ecjtu/ecjtu_api/schema.py
+++ b/ecjtu/ecjtu_api/schema.py
@@ -15,7 +15,7 @@ def __init__(
stud_id: Optional[str] = None,
token: str = None,
cookie: Optional[List[Dict]] = None,
- ):
+ ) -> None:
super().__init__()
self.stud_id = stud_id
self.token = token
diff --git a/ecjtu/utils/cookie.py b/ecjtu/utils/cookie.py
index 813d095..893fcbf 100644
--- a/ecjtu/utils/cookie.py
+++ b/ecjtu/utils/cookie.py
@@ -4,19 +4,19 @@
from httpx._types import CookieTypes
-def cookies_tolist(cookies: Optional[CookieTypes]):
+def cookies_tolist(cookies: Optional[CookieTypes]) -> list:
cookies_list = []
for cookie in cookies.jar:
- dict = {
+ cookie_dict = {
"name": cookie.name,
"value": cookie.value,
"domain": cookie.domain,
}
- cookies_list.append(dict)
+ cookies_list.append(cookie_dict)
return cookies_list
-def list_tocookie(cookies_list: list):
+def list_tocookie(cookies_list: list) -> httpx.Cookies:
cookies = httpx.Cookies()
for cookie in cookies_list:
cookies.set(**cookie)
diff --git a/examples/ecjtu-api.md b/examples/ecjtu-api.md
index 046703c..b50e312 100644
--- a/examples/ecjtu-api.md
+++ b/examples/ecjtu-api.md
@@ -33,7 +33,7 @@ Base URLs:
POST /login
-登录并获取token,以下所有接口都需要token才可以使用
+登录获取access_token和refresh_token,access_token用于之后的所有请求,refresh_token用于刷新access_token
> Body 请求参数
@@ -46,9 +46,40 @@ POST /login
### 请求参数
+|名称|位置|类型|必选|说明|
+|---|---|---|---|---|
+|body|body|object| 否 |none|
+
+> 返回示例
+
+> 200 Response
+
+```json
+"string"
+```
+
+### 返回结果
+
+|状态码|状态码含义|说明|数据模型|
+|---|---|---|---|
+|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Successful Response|string|
+|422|[Unprocessable Entity](https://tools.ietf.org/html/rfc2518#section-10.3)|Validation Error|Inline|
+
+### 返回数据结构
+
+
+
+## POST 刷新access_token
+
+POST /refresh_token
+
+刷新access_token
+
+### 请求参数
+
|名称|位置|类型|必选|中文名|说明|
|---|---|---|---|---|---|
-|body|body|[UserLoginSchema](#schemauserloginschema)| 否 | UserLoginSchema|none|
+|data|query|string| 否 | Data|none|
> 返回示例
@@ -63,7 +94,9 @@ POST /login
|状态码|状态码含义|说明|数据模型|
|---|---|---|---|
|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Successful Response|string|
-|422|[Unprocessable Entity](https://tools.ietf.org/html/rfc2518#section-10.3)|Validation Error|[HTTPValidationError](#schemahttpvalidationerror)|
+|422|[Unprocessable Entity](https://tools.ietf.org/html/rfc2518#section-10.3)|Validation Error|Inline|
+
+### 返回数据结构
# GPA
@@ -94,7 +127,9 @@ GET /gpa
|状态码|状态码含义|说明|数据模型|
|---|---|---|---|
|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Successful Response|string|
-|422|[Unprocessable Entity](https://tools.ietf.org/html/rfc2518#section-10.3)|Validation Error|[HTTPValidationError](#schemahttpvalidationerror)|
+|422|[Unprocessable Entity](https://tools.ietf.org/html/rfc2518#section-10.3)|Validation Error|Inline|
+
+### 返回数据结构
# 课表
@@ -125,7 +160,9 @@ GET /schedule
|状态码|状态码含义|说明|数据模型|
|---|---|---|---|
|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Successful Response|string|
-|422|[Unprocessable Entity](https://tools.ietf.org/html/rfc2518#section-10.3)|Validation Error|[HTTPValidationError](#schemahttpvalidationerror)|
+|422|[Unprocessable Entity](https://tools.ietf.org/html/rfc2518#section-10.3)|Validation Error|Inline|
+
+### 返回数据结构
@@ -155,13 +192,15 @@ GET /schedule/{date}
|状态码|状态码含义|说明|数据模型|
|---|---|---|---|
|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Successful Response|string|
-|422|[Unprocessable Entity](https://tools.ietf.org/html/rfc2518#section-10.3)|Validation Error|[HTTPValidationError](#schemahttpvalidationerror)|
+|422|[Unprocessable Entity](https://tools.ietf.org/html/rfc2518#section-10.3)|Validation Error|Inline|
+
+### 返回数据结构
## GET 获取本周课表
-GET /schedule/week
+GET /schedule_week
获取本周课表
@@ -184,7 +223,9 @@ GET /schedule/week
|状态码|状态码含义|说明|数据模型|
|---|---|---|---|
|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Successful Response|string|
-|422|[Unprocessable Entity](https://tools.ietf.org/html/rfc2518#section-10.3)|Validation Error|[HTTPValidationError](#schemahttpvalidationerror)|
+|422|[Unprocessable Entity](https://tools.ietf.org/html/rfc2518#section-10.3)|Validation Error|Inline|
+
+### 返回数据结构
# 成绩
@@ -215,7 +256,9 @@ GET /score
|状态码|状态码含义|说明|数据模型|
|---|---|---|---|
|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Successful Response|string|
-|422|[Unprocessable Entity](https://tools.ietf.org/html/rfc2518#section-10.3)|Validation Error|[HTTPValidationError](#schemahttpvalidationerror)|
+|422|[Unprocessable Entity](https://tools.ietf.org/html/rfc2518#section-10.3)|Validation Error|Inline|
+
+### 返回数据结构
@@ -245,7 +288,9 @@ GET /score/{semester}
|状态码|状态码含义|说明|数据模型|
|---|---|---|---|
|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Successful Response|string|
-|422|[Unprocessable Entity](https://tools.ietf.org/html/rfc2518#section-10.3)|Validation Error|[HTTPValidationError](#schemahttpvalidationerror)|
+|422|[Unprocessable Entity](https://tools.ietf.org/html/rfc2518#section-10.3)|Validation Error|Inline|
+
+### 返回数据结构
# 选课情况
@@ -276,7 +321,9 @@ GET /elective_courses
|状态码|状态码含义|说明|数据模型|
|---|---|---|---|
|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Successful Response|string|
-|422|[Unprocessable Entity](https://tools.ietf.org/html/rfc2518#section-10.3)|Validation Error|[HTTPValidationError](#schemahttpvalidationerror)|
+|422|[Unprocessable Entity](https://tools.ietf.org/html/rfc2518#section-10.3)|Validation Error|Inline|
+
+### 返回数据结构
@@ -306,4 +353,4 @@ GET /elective_courses/{semester}
|状态码|状态码含义|说明|数据模型|
|---|---|---|---|
|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Successful Response|string|
-|422|[Unprocessable Entity](https://tools.ietf.org/html/rfc2518#section-10.3)|Validation Error|[HTTPValidationError](#schemahttpvalidationerror)|
\ No newline at end of file
+|422|[Unprocessable Entity](https://tools.ietf.org/html/rfc2518#section-10.3)|Validation Error|Inline|
diff --git a/examples/ecjtu.api.ipynb b/examples/ecjtu.api.ipynb
deleted file mode 100644
index ffd520c..0000000
--- a/examples/ecjtu.api.ipynb
+++ /dev/null
@@ -1,114 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "# ecjtu-api\n",
- "我们提供了ecjtu的一套服务。\n",
- "通过fastapi的形式实现"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## 相关接口描述\n",
- "\n",
- "详细信息可以参考ecjtu-api.md。\n",
- "\n",
- "1. 登录\n",
- " \n",
- " post /login \n",
- " 通过学号和密码进行登录,获取登录token,其他接口的请求均要token认证\n",
- "2. gpa\n",
- " \n",
- " get /gpa\n",
- " 获取当前gpa情况\n",
- "3. 课表\n",
- " * get /schedule\n",
- " 获取今日课表\n",
- " * get /schedule/{date}\n",
- " 获取指定日期课表 date格式为2024-05-01\n",
- " * get /schedule/week\n",
- " 获取本周课表\n",
- "4. 成绩\n",
- " * get /score\n",
- " 获取目前成绩\n",
- " * /score/{semester}\n",
- " 获取指定学期成绩 semester格式为2023.1\n",
- "5. 选课情况\n",
- " * get /elective_courses\n",
- " 获取当前选课信息\n",
- " * get /elective_courses/{semester}\n",
- " 获取指定学期选课信息 semester格式为2023.1"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## 服务的启动\n",
- "我们提供了两种服务的启动方式\n",
- "\n",
- "### 通过python文件启动"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "from ecjtu.ecjtu_api.api import start_api_server\n",
- "\n",
- "def main():\n",
- " start_api_server(port=8080)\n",
- "\n",
- "if __name__ == '__main__':\n",
- " main()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## 通过命令行启动"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "vscode": {
- "languageId": "shellscript"
- }
- },
- "outputs": [],
- "source": [
- "python ecjtu/server.py --port 8080 "
- ]
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "ecjtut",
- "language": "python",
- "name": "python3"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.10.14"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 2
-}
From 0a43994f048ad4c1db2d4626cad6414f209740ad Mon Sep 17 00:00:00 2001
From: longsihua2003 <1161443436@qq.com>
Date: Fri, 10 May 2024 21:29:21 +0800
Subject: [PATCH 6/6] Add comments
---
ecjtu/ecjtu_api/api.py | 165 +++++++++++++++++++++++-------
ecjtu/ecjtu_api/auth.py | 65 ++++++++++--
ecjtu/ecjtu_api/middle.py | 8 +-
ecjtu/ecjtu_api/respose_result.py | 22 ++--
ecjtu/ecjtu_api/schema.py | 4 +-
ecjtu/server.py | 8 +-
examples/basic-usage.ipynb | 36 +++----
7 files changed, 219 insertions(+), 89 deletions(-)
diff --git a/ecjtu/ecjtu_api/api.py b/ecjtu/ecjtu_api/api.py
index f91ba9a..52d4194 100644
--- a/ecjtu/ecjtu_api/api.py
+++ b/ecjtu/ecjtu_api/api.py
@@ -3,10 +3,10 @@
from fastapi import FastAPI, Header
from fastapi.responses import RedirectResponse
+from starlette.responses import JSONResponse
from ecjtu.client import ECJTU
-
-from . import auth, middle, respose_result, schema
+from ecjtu.ecjtu_api import auth, middle, respose_result, schema
app = FastAPI(title="ECJTU API", description="API for ECJTU")
@@ -15,8 +15,8 @@
@app.get("/", include_in_schema=False)
def push_docs():
- respose = RedirectResponse(url="/docs")
- return respose
+ response = RedirectResponse(url="/docs")
+ return response
@app.post(
@@ -25,7 +25,15 @@ def push_docs():
summary="登录",
description="登录获取access_token和refresh_token,access_token用于之后的所有请求,refresh_token用于刷新access_token",
)
-def login(user: schema.UserLoginSchema):
+def login(user: schema.UserLoginSchema) -> JSONResponse:
+ """login
+
+ Args:
+ user (schema.UserLoginSchema): user login info
+
+ Returns:
+ JSONResponse: response
+ """
try:
access_token, refresh_token = auth.create_tokens(user.stud_id, user.password)
except Exception as e:
@@ -43,6 +51,15 @@ def login(user: schema.UserLoginSchema):
description="刷新access_token",
)
def refresh_token(data: str = None):
+ """refresh access token
+
+ Args:
+ data(str): refresh token
+
+ Returns:
+ JSONResponse: access token
+
+ """
try:
access_token = auth.refresh_access_token(data)
except Exception as e:
@@ -50,12 +67,33 @@ def refresh_token(data: str = None):
return respose_result.ResponseResult.success({"access_token": access_token})
-# gpa接口
-@app.get("/gpa", tags=["GPA"], summary="获取GPA", description="获取当学期GPA")
-def gpa(token: str = Header(None)):
+def create_client(token: str) -> ECJTU:
+ """create client
+
+ Args:
+ token (str): access token
+
+ Returns:
+ ECJTU: client
+ """
stud_id = auth.get_stud_id(token)
cookie = auth.get_cookie(stud_id)
client = ECJTU(cookie=cookie)
+ return client
+
+
+@app.get("/gpa", tags=["GPA"], summary="获取GPA", description="获取当学期GPA")
+def gpa(token: str = Header(None)):
+ """get gpa
+
+ Args:
+ token: access token
+
+ Returns:
+ JSONResponse: gpa
+
+ """
+ client = create_client(token)
try:
gpa = client.gpa.today()
except Exception as e:
@@ -63,12 +101,18 @@ def gpa(token: str = Header(None)):
return respose_result.ResponseResult.success(dict(gpa))
-# 课表接口
@app.get("/schedule", tags=["课表"], summary="获取当天课表", description="获取当天课表")
def schedule(token: str = Header(None)):
- stud_id = auth.get_stud_id(token)
- cookie = auth.get_cookie(stud_id)
- client = ECJTU(cookie=cookie)
+ """get schedule of today
+
+ Args:
+ token: access token
+
+ Returns:
+ JSONResponse: schedule of today
+
+ """
+ client = create_client(token)
try:
schedule = client.scheduled_courses.today()
except Exception as e:
@@ -86,15 +130,21 @@ def schedule(token: str = Header(None)):
description="获取指定日期课表,指定日期格式为yyyy-mm-dd",
)
def schedule_date(token: str = Header(None), date: str = None):
- # date(str): The date to filter, eg: 2023-01-01
+ """get schedule of specified date
+
+ Args:
+ token: access token
+ date: date in format yyyy-mm-dd
+
+ Returns:
+ JSONResponse: schedule of specified date
+
+ """
try:
valid_date = datetime.datetime.strptime(date, "%Y-%m-%d").date()
except ValueError:
return respose_result.ResponseResult.param_error("日期格式错误")
-
- stud_id = auth.get_stud_id(token)
- cookie = auth.get_cookie(stud_id)
- client = ECJTU(cookie=cookie)
+ client = create_client(token)
try:
scheduled_courses = client.scheduled_courses.filter(date=valid_date)
except Exception as e:
@@ -109,9 +159,16 @@ def schedule_date(token: str = Header(None), date: str = None):
"/schedule_week", tags=["课表"], summary="获取本周课表", description="获取本周课表"
)
def schedule_week(token: str = Header(None)):
- stud_id = auth.get_stud_id(token)
- cookie = auth.get_cookie(stud_id)
- client = ECJTU(cookie=cookie)
+ """get schedule of this week
+
+ Args:
+ token: access token
+
+ Returns:
+ JSONResponse: schedule of this week
+
+ """
+ client = create_client(token)
try:
schedule = client.scheduled_courses.this_week()
except Exception as e:
@@ -125,12 +182,18 @@ def schedule_week(token: str = Header(None)):
return respose_result.ResponseResult.success(dict_list)
-# 成绩接口
@app.get("/score", tags=["成绩"], summary="获取当前成绩", description="获取当学期成绩")
def score(token: str = Header(None)):
- stud_id = auth.get_stud_id(token)
- cookie = auth.get_cookie(stud_id)
- client = ECJTU(cookie=cookie)
+ """get score of this semester
+
+ Args:
+ token: access token
+
+ Returns:
+ JSONResponse: score of this semester
+
+ """
+ client = create_client(token)
try:
score = client.scores.today()
except Exception as e:
@@ -148,12 +211,19 @@ def score(token: str = Header(None)):
description="获取指定学期成绩,semester格式为yyyy.1或yyyy.2",
)
def score_semester(token: str = Header(None), semester: str = None):
- # semester(Optional[str]): The semester to filter, eg: 2023.1, 2023.2
+ """get score of specified semester
+
+ Args:
+ token: access token
+ semester: semester in format yyyy.1 or yyyy.2
+
+ Returns:
+ JSONResponse: score of specified semester
+
+ """
if not re.match(r"\d{4}\.[12]", semester):
return respose_result.ResponseResult.param_error("学期格式错误")
- stud_id = auth.get_stud_id(token)
- cookie = auth.get_cookie(stud_id)
- client = ECJTU(cookie=cookie)
+ client = create_client(token)
try:
scores = client.scores.filter(semester=semester)
except Exception as e:
@@ -164,7 +234,6 @@ def score_semester(token: str = Header(None), semester: str = None):
return respose_result.ResponseResult.success(score_list)
-# 选课情况接口
@app.get(
"/elective_courses",
tags=["选课情况"],
@@ -172,9 +241,16 @@ def score_semester(token: str = Header(None), semester: str = None):
description="获取当前学期选课情况",
)
def elective_courses(token: str = Header(None)):
- stud_id = auth.get_stud_id(token)
- cookie = auth.get_cookie(stud_id)
- client = ECJTU(cookie=cookie)
+ """get elective courses
+
+ Args:
+ token: access token
+
+ Returns:
+ JSONResponse: elective courses
+
+ """
+ client = create_client(token)
try:
elective_courses = client.elective_courses.today()
except Exception as e:
@@ -192,12 +268,18 @@ def elective_courses(token: str = Header(None)):
description="获取指定学期选课情况,semester格式为yyyy.1或yyyy.2",
)
def elective_courses_semester(token: str = Header(None), semester: str = None):
- # semester(Optional[str]): The semester to filter, eg: 2023.1, 2023.2
+ """get elective courses of specified semester
+
+ Args:
+ token: access token
+ semester: semester in format yyyy.1 or yyyy.2
+
+ Returns:
+ JSONResponse: elective courses
+ """
if not re.match(r"\d{4}\.[12]", semester):
return respose_result.ResponseResult.param_error("学期格式错误")
- stud_id = auth.get_stud_id(token)
- cookie = auth.get_cookie(stud_id)
- client = ECJTU(cookie=cookie)
+ client = create_client(token)
try:
elective_courses = client.elective_courses.filter(semester=semester)
except Exception as e:
@@ -208,8 +290,15 @@ def elective_courses_semester(token: str = Header(None), semester: str = None):
return respose_result.ResponseResult.success(elective_courses_list)
-# 启动api服务
-def start_api_server(port=8080):
+def start_api_server(port: int = 8080) -> None:
+ """start api server
+
+ Args:
+ port (int, optional): port to run the server on. Defaults to 8080.
+
+ Raises:
+ Exception: if port is already in use, an exception will be raised.
+ """
import uvicorn
uvicorn.run(app, host="127.0.0.1", port=port)
diff --git a/ecjtu/ecjtu_api/auth.py b/ecjtu/ecjtu_api/auth.py
index db3f1c3..5c389e8 100644
--- a/ecjtu/ecjtu_api/auth.py
+++ b/ecjtu/ecjtu_api/auth.py
@@ -5,24 +5,45 @@
from cushy_storage import CushyOrmCache
from ecjtu.client import ECJTU
+from ecjtu.ecjtu_api import schema
from ecjtu.utils.cookie import cookies_tolist, list_tocookie
from ecjtu.utils.logger import get_path
-from . import schema
-
def encode_data(data: str) -> str:
- # 将数据编码为base64字符串
+ """encode to string
+
+ Args:
+ data (str): string to encode
+
+ Returns:
+ str: encoded string
+ """
return base64.b64encode(data.encode()).decode()
def decode_data(encoded_data: str) -> str:
- # 解码base64字符串
+ """decode to string
+
+ Args:
+ encoded_data (str): encoded string
+
+ Returns:
+ str: decoded string
+ """
return base64.b64decode(encoded_data.encode()).decode()
-# 双token加密
def create_tokens(stud_id: str, pwd: str) -> tuple[str, str]:
+ """create access_token and refresh_token
+
+ Args:
+ stud_id (str): student id
+ pwd (str): password
+
+ Returns:
+ tuple[str, str]: access_token and refresh_token
+ """
access_time = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(
minutes=60
)
@@ -44,20 +65,30 @@ def create_tokens(stud_id: str, pwd: str) -> tuple[str, str]:
stud_file = CushyOrmCache(get_path())
stud = stud_file.query("FileAuth").filter(stud_id=stud_id).first()
- # 创建用户储存信息
+ # if not exist, create a new one
if not stud:
stud = schema.FileAuth(stud_id, access_token, cookie_list)
stud_file.add(stud)
return access_token, refresh_token
- # 更新用户信息
+ # update the old one
stud.cookie = cookie_list
stud.token = access_token
stud_file.update_obj(stud)
return access_token, refresh_token
-# 刷新access_token
def refresh_access_token(refresh_token: str) -> str:
+ """refresh access_token
+
+ Args:
+ refresh_token (str): refresh_token
+
+ Returns:
+ str: access_token
+
+ Raises:
+ Exception: if token is invalid or expired
+ """
data = decode_data(refresh_token)
print(data)
stud_id = data.split(":")[0]
@@ -78,8 +109,15 @@ def refresh_access_token(refresh_token: str) -> str:
return create_tokens(stud_id, pwd)[0]
-# 验证和读取access_token的stud_id
def get_stud_id(access_token: str) -> str:
+ """get stud_id from access_token
+
+ Args:
+ access_token (str): access_token
+
+ Returns:
+ str: stud_id
+ """
try:
data = decode_data(access_token)
stud_file = CushyOrmCache(get_path())
@@ -100,8 +138,15 @@ def get_stud_id(access_token: str) -> str:
return stud_id
-# 读取cookie
def get_cookie(stud_id: str) -> httpx.Cookies:
+ """get cookie from stud_id
+
+ Args:
+ stud_id (str): stud_id
+
+ Returns:
+ httpx.Cookies: cookies
+ """
stud_file = CushyOrmCache(get_path())
stud = stud_file.query("FileAuth").filter(stud_id=stud_id).first()
cookies = list_tocookie(stud.cookie)
diff --git a/ecjtu/ecjtu_api/middle.py b/ecjtu/ecjtu_api/middle.py
index c97e98a..b68d032 100644
--- a/ecjtu/ecjtu_api/middle.py
+++ b/ecjtu/ecjtu_api/middle.py
@@ -1,11 +1,11 @@
from starlette.middleware.base import BaseHTTPMiddleware
-from . import auth, respose_result
+from ecjtu.ecjtu_api import auth, respose_result
class MyMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
- # 不被拦截的路由
+ # not secure routes
secure_routes = [
"/refresh_token",
"/login",
@@ -21,10 +21,10 @@ async def dispatch(self, request, call_next):
if path not in secure_routes:
header = request.headers.get("token")
- # 是否存在token
+ # if not header, return response
if not header:
return response
- # 是否过期
+ # get stud_id from access_token
try:
stud_id = auth.get_stud_id(header)
except Exception as e:
diff --git a/ecjtu/ecjtu_api/respose_result.py b/ecjtu/ecjtu_api/respose_result.py
index 0bb3722..7672aa6 100644
--- a/ecjtu/ecjtu_api/respose_result.py
+++ b/ecjtu/ecjtu_api/respose_result.py
@@ -1,13 +1,11 @@
-from typing import Any, Dict, Union
+from typing import Any
from fastapi.responses import JSONResponse
class ResponseResult:
@staticmethod
- def success(
- data: Union[Dict[str, Any], Any] = None, msg: str = "success"
- ) -> JSONResponse:
+ def success(data: Any = None, msg: str = "success") -> JSONResponse:
return JSONResponse(content={"code": 200, "msg": msg, "data": data})
@staticmethod
@@ -15,33 +13,25 @@ def success_no_data(msg: str = "success") -> JSONResponse:
return JSONResponse(content={"code": 200, "msg": msg, "data": None})
@staticmethod
- def not_found(
- data: Union[Dict[str, Any], Any] = None, msg: str = "not found"
- ) -> JSONResponse:
+ def not_found(data: str = None, msg: str = "not found") -> JSONResponse:
return JSONResponse(
status_code=404, content={"code": 404, "msg": msg, "data": data}
)
@staticmethod
- def auth_error(
- data: Union[Dict[str, Any], Any] = None, msg: str = "auth error"
- ) -> JSONResponse:
+ def auth_error(data: str = None, msg: str = "auth error") -> JSONResponse:
return JSONResponse(
status_code=401, content={"code": 401, "msg": msg, "data": data}
)
@staticmethod
- def param_error(
- data: Union[Dict[str, Any], Any] = None, msg: str = "param error"
- ) -> JSONResponse:
+ def param_error(data: str = None, msg: str = "param error") -> JSONResponse:
return JSONResponse(
status_code=400, content={"code": 400, "msg": msg, "data": data}
)
@staticmethod
- def error(
- data: Union[Dict[str, Any], Any] = None, msg: str = "error"
- ) -> JSONResponse:
+ def error(data: str = None, msg: str = "error") -> JSONResponse:
return JSONResponse(
status_code=500, content={"code": 500, "msg": msg, "data": data}
)
diff --git a/ecjtu/ecjtu_api/schema.py b/ecjtu/ecjtu_api/schema.py
index 82fc442..9d904e0 100644
--- a/ecjtu/ecjtu_api/schema.py
+++ b/ecjtu/ecjtu_api/schema.py
@@ -5,8 +5,8 @@
class UserLoginSchema(BaseModel):
- stud_id: str = Field(..., min_length=1)
- password: str = Field(..., min_length=1)
+ stud_id: str = Field(..., min_length=1, description="学号")
+ password: str = Field(..., min_length=1, description="密码")
class FileAuth(BaseORMModel):
diff --git a/ecjtu/server.py b/ecjtu/server.py
index 8317631..1e9d680 100644
--- a/ecjtu/server.py
+++ b/ecjtu/server.py
@@ -1,9 +1,15 @@
import argparse
-from ecjtu_api.api import start_api_server
+from ecjtu.ecjtu_api.api import start_api_server
def main():
+ """start the api server from the command line
+
+ Usage:
+ python ecjtu.server.py --port 8000
+
+ """
parser = argparse.ArgumentParser(description="ECJTU Command Line Interface")
parser.add_argument(
"--port", type=int, default=8000, help="Port to run the server on"
diff --git a/examples/basic-usage.ipynb b/examples/basic-usage.ipynb
index 3892378..01a6180 100644
--- a/examples/basic-usage.ipynb
+++ b/examples/basic-usage.ipynb
@@ -23,7 +23,6 @@
"start_time": "2024-04-19T21:22:41.539373Z"
}
},
- "outputs": [],
"source": [
"from typing import List\n",
"\n",
@@ -31,7 +30,8 @@
"from ecjtu.models import ScheduledCourse, Score, GPA\n",
"\n",
"client = ECJTU(stud_id=\"xxx\", password=\"xxx\")"
- ]
+ ],
+ "outputs": []
},
{
"cell_type": "markdown",
@@ -89,7 +89,6 @@
},
{
"cell_type": "code",
- "outputs": [],
"source": [
"courses: List[ScheduledCourse] = client.scheduled_courses.today()\n",
"print(courses)"
@@ -98,7 +97,8 @@
"collapsed": false
},
"id": "e2f416384ab1ead5",
- "execution_count": null
+ "execution_count": null,
+ "outputs": []
},
{
"cell_type": "markdown",
@@ -112,7 +112,6 @@
},
{
"cell_type": "code",
- "outputs": [],
"source": [
"courses: List[List[ScheduledCourse]] = client.scheduled_courses.this_week()\n",
"\n",
@@ -125,7 +124,8 @@
"collapsed": false
},
"id": "3f83ebf27ada48a0",
- "execution_count": null
+ "execution_count": null,
+ "outputs": []
},
{
"cell_type": "markdown",
@@ -139,7 +139,6 @@
},
{
"cell_type": "code",
- "outputs": [],
"source": [
"courses: List[ScheduledCourse] = client.scheduled_courses.filter(date=\"2023-04-15\")\n",
"\n",
@@ -149,7 +148,8 @@
"collapsed": false
},
"id": "7dabdb8809a45c1c",
- "execution_count": null
+ "execution_count": null,
+ "outputs": []
},
{
"cell_type": "markdown",
@@ -167,7 +167,6 @@
},
{
"cell_type": "code",
- "outputs": [],
"source": [
"scores: List[Score] = client.scores.today()\n",
"print(scores)"
@@ -176,7 +175,8 @@
"collapsed": false
},
"id": "a1faace016c0a7bd",
- "execution_count": null
+ "execution_count": null,
+ "outputs": []
},
{
"cell_type": "markdown",
@@ -190,7 +190,6 @@
},
{
"cell_type": "code",
- "outputs": [],
"source": [
"scores: List[Score] = client.scores.filter(semester=\"2022.1\")\n",
"\n",
@@ -200,7 +199,8 @@
"collapsed": false
},
"id": "a0cf75a1b43d34e6",
- "execution_count": null
+ "execution_count": null,
+ "outputs": []
},
{
"cell_type": "markdown",
@@ -224,7 +224,6 @@
},
{
"cell_type": "code",
- "outputs": [],
"source": [
"gpa: GPA = client.gpa.today()\n",
"\n",
@@ -234,7 +233,8 @@
"collapsed": false
},
"id": "2cf50ef9ef5450fb",
- "execution_count": null
+ "execution_count": null,
+ "outputs": []
},
{
"cell_type": "markdown",
@@ -248,7 +248,6 @@
},
{
"cell_type": "code",
- "outputs": [],
"source": [
"courses = client.elective_courses.today()\n",
"\n",
@@ -259,7 +258,8 @@
"collapsed": false
},
"id": "b95c850446242749",
- "execution_count": null
+ "execution_count": null,
+ "outputs": []
},
{
"cell_type": "markdown",
@@ -273,7 +273,6 @@
},
{
"cell_type": "code",
- "outputs": [],
"source": [
"courses = client.elective_courses.filter(semester=\"2022.1\")\n",
"\n",
@@ -284,7 +283,8 @@
"collapsed": false
},
"id": "c81e6ab4e350f79e",
- "execution_count": null
+ "execution_count": null,
+ "outputs": []
},
{
"cell_type": "markdown",