Compare commits
2 Commits
a83befe109
...
b58f74d59b
| Author | SHA1 | Date | |
|---|---|---|---|
| b58f74d59b | |||
| 86b030930c |
@@ -0,0 +1,15 @@
|
|||||||
|
from fastapi import APIRouter
|
||||||
|
from .deps import get_current_user
|
||||||
|
from .jwt import create_jwt_token, create_refresh_token, verify_access_token
|
||||||
|
|
||||||
|
# 统一创建并导出路由器,满足 main.py 的 include_router 需求
|
||||||
|
router = APIRouter(tags=["Authentication"])
|
||||||
|
|
||||||
|
# 显式声明这个包对外暴露的接口
|
||||||
|
__all__ = [
|
||||||
|
"router",
|
||||||
|
"get_current_user",
|
||||||
|
"create_jwt_token",
|
||||||
|
"create_refresh_token",
|
||||||
|
"verify_access_token",
|
||||||
|
]
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
from fastapi import Request, HTTPException
|
from fastapi import Request, HTTPException
|
||||||
import app.auth.jwt as jwt
|
|
||||||
import os
|
import os
|
||||||
|
from . import jwt
|
||||||
|
|
||||||
JWT_SECRET = os.getenv("JWT_SECRET")
|
JWT_SECRET = os.getenv("JWT_SECRET")
|
||||||
JWT_ALGORITHM = os.getenv("JWT_ALGORITHM", "HS256")
|
JWT_ALGORITHM = os.getenv("JWT_ALGORITHM", "HS256")
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
import app.auth.jwt as jwt
|
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
import os
|
import os
|
||||||
|
import jwt
|
||||||
from fastapi import HTTPException, status
|
from fastapi import HTTPException, status
|
||||||
|
|
||||||
JWT_SECRET = os.getenv("JWT_SECRET")
|
JWT_SECRET = os.getenv("JWT_SECRET")
|
||||||
|
|||||||
@@ -1,8 +1,22 @@
|
|||||||
import os
|
import os
|
||||||
from sqlalchemy import create_engine
|
from sqlalchemy import create_engine
|
||||||
from sqlalchemy.orm import sessionmaker
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
# 加载 .env 文件
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
DATABASE_URL = os.getenv("DATABASE_URL")
|
DATABASE_URL = os.getenv("DATABASE_URL")
|
||||||
|
|
||||||
|
# 自动将旧版的 postgres:// 替换为 SQLAlchemy 强要求的 postgresql://
|
||||||
|
if DATABASE_URL and DATABASE_URL.startswith("postgres://"):
|
||||||
|
DATABASE_URL = DATABASE_URL.replace("postgres://", "postgresql://", 1)
|
||||||
|
elif DATABASE_URL and DATABASE_URL.startswith("postgres+"):
|
||||||
|
DATABASE_URL = DATABASE_URL.replace("postgres+", "postgresql+", 1)
|
||||||
|
|
||||||
|
# 如果环境变量彻底缺失,做一个安全的报错提示
|
||||||
|
if not DATABASE_URL:
|
||||||
|
raise ValueError("DATABASE_URL environment variable is not set or empty.")
|
||||||
|
|
||||||
engine = create_engine(DATABASE_URL)
|
engine = create_engine(DATABASE_URL)
|
||||||
SessionLocal = sessionmaker(bind=engine)
|
SessionLocal = sessionmaker(bind=engine)
|
||||||
+2
-5
@@ -24,8 +24,5 @@ schema = Schema(query=Query, mutation=Mutation)
|
|||||||
graphql_router = GraphQLRouter(schema, context_getter=get_context)
|
graphql_router = GraphQLRouter(schema, context_getter=get_context)
|
||||||
|
|
||||||
# 挂载路由
|
# 挂载路由
|
||||||
# 保留现有的 auth 路由供 Google OAuth 回调使用
|
app.include_router(auth_router) # 保留现有的 auth 路由供 Google OAuth 回调使用
|
||||||
app.include_router(auth_router)
|
app.include_router(graphql_router, prefix="/graphql") # 挂载 GraphQL 终点,前端以后只需要请求 /graphql
|
||||||
|
|
||||||
# 挂载 GraphQL 终点,前端以后只需要请求 /graphql
|
|
||||||
app.include_router(graphql_router, prefix="/graphql")
|
|
||||||
+8
-6
@@ -56,22 +56,24 @@ class AutoID:
|
|||||||
不再需要手动定义任何 prefix,全自动根据 __tablename__ 编码生成
|
不再需要手动定义任何 prefix,全自动根据 __tablename__ 编码生成
|
||||||
支持子类自定义长度
|
支持子类自定义长度
|
||||||
"""
|
"""
|
||||||
# 默认配置
|
# 全局固定配置
|
||||||
_id_prefix_len = 3 # 前缀长度
|
ID_PREFIX_LEN = 3
|
||||||
_id_payload_len = 22 # 主体长度
|
ID_PAYLOAD_LEN = 22
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def id_length(cls) -> int:
|
def id_length(cls) -> int:
|
||||||
"""返回当前类 ID 的总长度"""
|
"""返回当前类 ID 的总长度"""
|
||||||
return cls._id_prefix_len + cls._id_payload_len
|
return cls.ID_PREFIX_LEN + cls.ID_PAYLOAD_LEN
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def id(cls):
|
def id(cls):
|
||||||
# 运行时动态获取子类的 __tablename__
|
# 运行时动态获取子类的 __tablename__
|
||||||
tablename = cls.__tablename__
|
tablename = cls.__tablename__
|
||||||
|
|
||||||
# 获取子类配置的长度
|
# 获取子类配置的长度
|
||||||
p_len = cls._id_prefix_len
|
p_len = AutoID.ID_PREFIX_LEN
|
||||||
s_len = cls._id_payload_len
|
s_len = AutoID.ID_PAYLOAD_LEN
|
||||||
|
|
||||||
prefix = encode_tablename_to_prefix(tablename, length=p_len)
|
prefix = encode_tablename_to_prefix(tablename, length=p_len)
|
||||||
|
|
||||||
# 核心工厂函数:拼接【编码后的表前缀】和【递增Payload】
|
# 核心工厂函数:拼接【编码后的表前缀】和【递增Payload】
|
||||||
|
|||||||
@@ -1,25 +1,18 @@
|
|||||||
from typing import TYPE_CHECKING
|
|
||||||
from sqlalchemy import Column, DateTime, String, ForeignKey, UniqueConstraint
|
from sqlalchemy import Column, DateTime, String, ForeignKey, UniqueConstraint
|
||||||
from sqlalchemy.sql import func
|
from sqlalchemy.sql import func
|
||||||
from .base import AutoID, Base
|
from .base import AutoID, Base
|
||||||
|
|
||||||
# 只有在静态检查时才导入
|
|
||||||
# 运行阶段这段代码会被跳过,彻底避免循环导入
|
|
||||||
# 不确定是否用得上,但感觉这是个好习惯,尤其在模型之间有外键关系时
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from .user import User
|
|
||||||
|
|
||||||
class Follow(Base, AutoID):
|
class Follow(Base, AutoID):
|
||||||
"""用户关注关系表"""
|
"""用户关注关系表"""
|
||||||
__tablename__ = "follows"
|
__tablename__ = "follows"
|
||||||
|
|
||||||
follower_id = Column(
|
follower_id = Column(
|
||||||
String(User.id_length()),
|
String(AutoID.id_length()),
|
||||||
ForeignKey("users.id", ondelete="CASCADE"),
|
ForeignKey("users.id", ondelete="CASCADE"),
|
||||||
index=True
|
index=True
|
||||||
)
|
)
|
||||||
followee_id = Column(
|
followee_id = Column(
|
||||||
String(User.id_length()),
|
String(AutoID.id_length()),
|
||||||
ForeignKey("users.id", ondelete="CASCADE"),
|
ForeignKey("users.id", ondelete="CASCADE"),
|
||||||
index=True
|
index=True
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,24 +1,18 @@
|
|||||||
from typing import TYPE_CHECKING
|
|
||||||
from sqlalchemy import Column, DateTime, String, ForeignKey, UniqueConstraint
|
from sqlalchemy import Column, DateTime, String, ForeignKey, UniqueConstraint
|
||||||
from sqlalchemy.sql import func
|
from sqlalchemy.sql import func
|
||||||
from .base import AutoID, Base
|
from .base import AutoID, Base
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from .user import User
|
|
||||||
from .project import ProjectItem
|
|
||||||
from .workspace import Workspace
|
|
||||||
|
|
||||||
class ProjectLike(Base, AutoID):
|
class ProjectLike(Base, AutoID):
|
||||||
"""项目点赞表"""
|
"""项目点赞表"""
|
||||||
__tablename__ = "project_likes"
|
__tablename__ = "project_likes"
|
||||||
|
|
||||||
user_id = Column(
|
user_id = Column(
|
||||||
String(User.id_length()),
|
String(AutoID.id_length()),
|
||||||
ForeignKey("users.id", ondelete="CASCADE"),
|
ForeignKey("users.id", ondelete="CASCADE"),
|
||||||
index=True
|
index=True
|
||||||
)
|
)
|
||||||
project_item_id = Column(
|
project_item_id = Column(
|
||||||
String(ProjectItem.id_length()),
|
String(AutoID.id_length()),
|
||||||
ForeignKey("project_items.id", ondelete="CASCADE"),
|
ForeignKey("project_items.id", ondelete="CASCADE"),
|
||||||
index=True
|
index=True
|
||||||
)
|
)
|
||||||
@@ -34,12 +28,12 @@ class ProjectFavorite(Base, AutoID):
|
|||||||
__tablename__ = "project_favorites"
|
__tablename__ = "project_favorites"
|
||||||
|
|
||||||
user_id = Column(
|
user_id = Column(
|
||||||
String(User.id_length()),
|
String(AutoID.id_length()),
|
||||||
ForeignKey("users.id", ondelete="CASCADE"),
|
ForeignKey("users.id", ondelete="CASCADE"),
|
||||||
index=True
|
index=True
|
||||||
)
|
)
|
||||||
project_item_id = Column(
|
project_item_id = Column(
|
||||||
String(ProjectItem.id_length()),
|
String(AutoID.id_length()),
|
||||||
ForeignKey("project_items.id", ondelete="CASCADE"),
|
ForeignKey("project_items.id", ondelete="CASCADE"),
|
||||||
index=True
|
index=True
|
||||||
)
|
)
|
||||||
@@ -55,12 +49,12 @@ class ProjectShare(Base, AutoID):
|
|||||||
__tablename__ = "project_shares"
|
__tablename__ = "project_shares"
|
||||||
|
|
||||||
user_id = Column(
|
user_id = Column(
|
||||||
String(User.id_length()),
|
String(AutoID.id_length()),
|
||||||
ForeignKey("users.id", ondelete="CASCADE"),
|
ForeignKey("users.id", ondelete="CASCADE"),
|
||||||
index=True
|
index=True
|
||||||
)
|
)
|
||||||
project_item_id = Column(
|
project_item_id = Column(
|
||||||
String(ProjectItem.id_length()),
|
String(AutoID.id_length()),
|
||||||
ForeignKey("project_items.id", ondelete="CASCADE"),
|
ForeignKey("project_items.id", ondelete="CASCADE"),
|
||||||
index=True
|
index=True
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,18 +1,13 @@
|
|||||||
from typing import TYPE_CHECKING
|
|
||||||
from sqlalchemy import Column, DateTime, String, ForeignKey, BigInteger
|
from sqlalchemy import Column, DateTime, String, ForeignKey, BigInteger
|
||||||
from sqlalchemy.sql import func
|
from sqlalchemy.sql import func
|
||||||
from .base import AutoID, Base
|
from .base import AutoID, Base
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from .user import User
|
|
||||||
from .workspace import Workspace
|
|
||||||
|
|
||||||
class LibraryItem(Base, AutoID):
|
class LibraryItem(Base, AutoID):
|
||||||
"""工作区共享媒体/文件资源库"""
|
"""工作区共享媒体/文件资源库"""
|
||||||
__tablename__ = "library_items"
|
__tablename__ = "library_items"
|
||||||
|
|
||||||
workspace_id = Column(
|
workspace_id = Column(
|
||||||
String(Workspace.id_length()),
|
String(AutoID.id_length()),
|
||||||
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
||||||
index=True,
|
index=True,
|
||||||
)
|
)
|
||||||
@@ -34,7 +29,7 @@ class LibraryItem(Base, AutoID):
|
|||||||
mime_type = Column(String, nullable=True) # 媒体类型,例如 'image/jpeg', 'video/mp4', 'application/pdf'
|
mime_type = Column(String, nullable=True) # 媒体类型,例如 'image/jpeg', 'video/mp4', 'application/pdf'
|
||||||
|
|
||||||
uploaded_by = Column(
|
uploaded_by = Column(
|
||||||
String(User.id_length()),
|
String(AutoID.id_length()),
|
||||||
ForeignKey("users.id", ondelete="SET NULL"),
|
ForeignKey("users.id", ondelete="SET NULL"),
|
||||||
nullable=True,
|
nullable=True,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,17 +1,13 @@
|
|||||||
from typing import TYPE_CHECKING
|
|
||||||
from sqlalchemy import Column, DateTime, String, ForeignKey, Integer
|
from sqlalchemy import Column, DateTime, String, ForeignKey, Integer
|
||||||
from sqlalchemy.sql import func
|
from sqlalchemy.sql import func
|
||||||
from .base import AutoID, Base
|
from .base import AutoID, Base
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from .workspace import Workspace
|
|
||||||
|
|
||||||
class ProjectItem(Base, AutoID):
|
class ProjectItem(Base, AutoID):
|
||||||
"""项目与文件夹的树状虚拟文件系统"""
|
"""项目与文件夹的树状虚拟文件系统"""
|
||||||
__tablename__ = "project_items"
|
__tablename__ = "project_items"
|
||||||
|
|
||||||
workspace_id = Column(
|
workspace_id = Column(
|
||||||
String(Workspace.id_length()),
|
String(AutoID.id_length()),
|
||||||
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
||||||
index=True,
|
index=True,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
from typing import TYPE_CHECKING
|
|
||||||
from sqlalchemy import Column, DateTime, String, ForeignKey, BigInteger, UniqueConstraint, Integer
|
from sqlalchemy import Column, DateTime, String, ForeignKey, BigInteger, UniqueConstraint, Integer
|
||||||
from sqlalchemy.sql import func
|
from sqlalchemy.sql import func
|
||||||
from .base import AutoID, Base
|
from .base import AutoID, Base
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from .user import User
|
|
||||||
|
|
||||||
class Workspace(Base, AutoID):
|
class Workspace(Base, AutoID):
|
||||||
"""工作区表"""
|
"""工作区表"""
|
||||||
__tablename__ = "workspaces"
|
__tablename__ = "workspaces"
|
||||||
@@ -34,13 +30,13 @@ class WorkspaceMember(Base, AutoID):
|
|||||||
__tablename__ = "workspace_members"
|
__tablename__ = "workspace_members"
|
||||||
|
|
||||||
workspace_id = Column(
|
workspace_id = Column(
|
||||||
String(Workspace.id_length()),
|
String(AutoID.id_length()),
|
||||||
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
ForeignKey("workspaces.id", ondelete="CASCADE"),
|
||||||
index=True,
|
index=True,
|
||||||
)
|
)
|
||||||
# 允许 user_id 为空,因为被邀请的人可能还没注册系统,我们先记录他的邮箱
|
# 允许 user_id 为空,因为被邀请的人可能还没注册系统,我们先记录他的邮箱
|
||||||
user_id = Column(
|
user_id = Column(
|
||||||
String(User.id_length()),
|
String(AutoID.id_length()),
|
||||||
ForeignKey("users.id", ondelete="CASCADE"),
|
ForeignKey("users.id", ondelete="CASCADE"),
|
||||||
nullable=True,
|
nullable=True,
|
||||||
index=True,
|
index=True,
|
||||||
|
|||||||
@@ -7,8 +7,10 @@ requires-python = ">=3.14"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"alembic>=1.18.4",
|
"alembic>=1.18.4",
|
||||||
"boto3>=1.43.9",
|
"boto3>=1.43.9",
|
||||||
|
"dotenv>=0.9.9",
|
||||||
"fastapi>=0.136.1",
|
"fastapi>=0.136.1",
|
||||||
"httpx>=0.28.1",
|
"httpx>=0.28.1",
|
||||||
|
"jwt>=1.4.0",
|
||||||
"oss2>=2.19.1",
|
"oss2>=2.19.1",
|
||||||
"psycopg2-binary>=2.9.12",
|
"psycopg2-binary>=2.9.12",
|
||||||
"python-jose>=3.5.0",
|
"python-jose>=3.5.0",
|
||||||
|
|||||||
@@ -145,8 +145,10 @@ source = { virtual = "." }
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "alembic" },
|
{ name = "alembic" },
|
||||||
{ name = "boto3" },
|
{ name = "boto3" },
|
||||||
|
{ name = "dotenv" },
|
||||||
{ name = "fastapi" },
|
{ name = "fastapi" },
|
||||||
{ name = "httpx" },
|
{ name = "httpx" },
|
||||||
|
{ name = "jwt" },
|
||||||
{ name = "oss2" },
|
{ name = "oss2" },
|
||||||
{ name = "psycopg2-binary" },
|
{ name = "psycopg2-binary" },
|
||||||
{ name = "python-jose" },
|
{ name = "python-jose" },
|
||||||
@@ -160,8 +162,10 @@ dependencies = [
|
|||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "alembic", specifier = ">=1.18.4" },
|
{ name = "alembic", specifier = ">=1.18.4" },
|
||||||
{ name = "boto3", specifier = ">=1.43.9" },
|
{ name = "boto3", specifier = ">=1.43.9" },
|
||||||
|
{ name = "dotenv", specifier = ">=0.9.9" },
|
||||||
{ name = "fastapi", specifier = ">=0.136.1" },
|
{ name = "fastapi", specifier = ">=0.136.1" },
|
||||||
{ name = "httpx", specifier = ">=0.28.1" },
|
{ name = "httpx", specifier = ">=0.28.1" },
|
||||||
|
{ name = "jwt", specifier = ">=1.4.0" },
|
||||||
{ name = "oss2", specifier = ">=2.19.1" },
|
{ name = "oss2", specifier = ">=2.19.1" },
|
||||||
{ name = "psycopg2-binary", specifier = ">=2.9.12" },
|
{ name = "psycopg2-binary", specifier = ">=2.9.12" },
|
||||||
{ name = "python-jose", specifier = ">=3.5.0" },
|
{ name = "python-jose", specifier = ">=3.5.0" },
|
||||||
@@ -304,6 +308,17 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/9d/9a/0fea98a70cf1749d41d738836f6349d97945f7c89433a259a6c2642eefeb/cryptography-48.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:16cd65b9330583e4619939b3a3843eec1e6e789744bb01e7c7e2e62e33c239c8", size = 3792100, upload-time = "2026-05-04T22:59:14.884Z" },
|
{ url = "https://files.pythonhosted.org/packages/9d/9a/0fea98a70cf1749d41d738836f6349d97945f7c89433a259a6c2642eefeb/cryptography-48.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:16cd65b9330583e4619939b3a3843eec1e6e789744bb01e7c7e2e62e33c239c8", size = 3792100, upload-time = "2026-05-04T22:59:14.884Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dotenv"
|
||||||
|
version = "0.9.9"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "python-dotenv" },
|
||||||
|
]
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b2/b7/545d2c10c1fc15e48653c91efde329a790f2eecfbbf2bd16003b5db2bab0/dotenv-0.9.9-py2.py3-none-any.whl", hash = "sha256:29cf74a087b31dafdb5a446b6d7e11cbce8ed2741540e2339c69fbef92c94ce9", size = 1892, upload-time = "2025-02-19T22:15:01.647Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ecdsa"
|
name = "ecdsa"
|
||||||
version = "0.19.2"
|
version = "0.19.2"
|
||||||
@@ -419,6 +434,18 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/07/cb/5f001272b6faeb23c1c9e0acc04d48eaaf5c862c17709d20e3469c6e0139/jmespath-0.10.0-py2.py3-none-any.whl", hash = "sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f", size = 24489, upload-time = "2020-05-12T22:03:45.643Z" },
|
{ url = "https://files.pythonhosted.org/packages/07/cb/5f001272b6faeb23c1c9e0acc04d48eaaf5c862c17709d20e3469c6e0139/jmespath-0.10.0-py2.py3-none-any.whl", hash = "sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f", size = 24489, upload-time = "2020-05-12T22:03:45.643Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jwt"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "cryptography" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/7f/20/21254c9e601e6c29445d1e8854c2a81bdb554e07a82fb1f9846137a6965c/jwt-1.4.0.tar.gz", hash = "sha256:f6f789128ac247142c79ee10f3dba6e366ec4e77c9920d18c1592e28aa0a7952", size = 24911, upload-time = "2025-06-23T13:28:38.289Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b9/80/34e3fae850adb0b7b8b9b1cf02b2d975fcb68e0e8eb7d56d6b4fc23f7433/jwt-1.4.0-py3-none-any.whl", hash = "sha256:7560a7f1de4f90de94ac645ee0303ac60c95b9e08e058fb69f6c330f71d71b11", size = 18248, upload-time = "2025-06-23T13:28:37.012Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mako"
|
name = "mako"
|
||||||
version = "1.3.12"
|
version = "1.3.12"
|
||||||
@@ -608,6 +635,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
|
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "python-dotenv"
|
||||||
|
version = "1.2.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "python-jose"
|
name = "python-jose"
|
||||||
version = "3.5.0"
|
version = "3.5.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user