fastapi-authly 0.1.2__tar.gz → 0.1.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/PKG-INFO +3 -1
- fastapi_authly-0.1.4/examples/correct_usage.py +74 -0
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/pyproject.toml +3 -1
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/src/fastapi_authly/__about__.py +1 -1
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/src/fastapi_authly/__init__.py +4 -2
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/src/fastapi_authly/auth.py +4 -2
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/src/fastapi_authly/core/__init__.py +2 -1
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/src/fastapi_authly/core/config.py +48 -5
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/src/fastapi_authly/core/security.py +8 -1
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/src/fastapi_authly/docs.py +20 -0
- fastapi_authly-0.1.4/src/fastapi_authly/static/favicon.svg +1 -0
- fastapi_authly-0.1.4/test.py +73 -0
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/uv.lock +19 -1
- fastapi_authly-0.1.2/CHANGELOG_SCALAR.md +0 -84
- fastapi_authly-0.1.2/docs/SCALAR_DOCS_USAGE.md +0 -121
- fastapi_authly-0.1.2/examples/complete_example.py +0 -63
- fastapi_authly-0.1.2/examples/use_scalar_docs.py +0 -53
- fastapi_authly-0.1.2/test.py +0 -177
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/.gitignore +0 -0
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/LICENSE +0 -0
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/README.md +0 -0
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/README.zh.md +0 -0
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/coverage.xml +0 -0
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/docs//345/207/275/346/225/260/345/274/217/347/274/226/347/250/213/344/270/203/346/255/246/345/231/250.md" +0 -0
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/htmlcov/.gitignore +0 -0
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/htmlcov/class_index.html +0 -0
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/htmlcov/coverage_html_cb_dd2e7eb5.js +0 -0
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/htmlcov/favicon_32_cb_c827f16f.png +0 -0
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/htmlcov/function_index.html +0 -0
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/htmlcov/index.html +0 -0
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/htmlcov/keybd_closed_cb_900cfef5.png +0 -0
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/htmlcov/status.json +0 -0
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/htmlcov/style_cb_9ff733b0.css +0 -0
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/htmlcov/z_af1bec017750c6fc___init___py.html +0 -0
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/htmlcov/z_af1bec017750c6fc_user_py.html +0 -0
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/htmlcov/z_b9d93864b1b0ad6e___about___py.html +0 -0
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/htmlcov/z_b9d93864b1b0ad6e___init___py.html +0 -0
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/htmlcov/z_b9d93864b1b0ad6e_auth_py.html +0 -0
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/htmlcov/z_b9d93864b1b0ad6e_interfaces_py.html +0 -0
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/htmlcov/z_d015ea9b27b0258e___init___py.html +0 -0
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/htmlcov/z_d015ea9b27b0258e_config_py.html +0 -0
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/htmlcov/z_d015ea9b27b0258e_security_py.html +0 -0
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/htmlcov/z_ddd01122054512b0___init___py.html +0 -0
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/htmlcov/z_ddd01122054512b0_tortoise_pg_py.html +0 -0
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/htmlcov/z_feee2d9ae7f7fd96___init___py.html +0 -0
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/htmlcov/z_feee2d9ae7f7fd96_user_py.html +0 -0
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/src/fastapi_authly/contrib/__init__.py +0 -0
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/src/fastapi_authly/contrib/tortoise_pg.py +0 -0
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/src/fastapi_authly/deps.py +0 -0
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/src/fastapi_authly/interfaces.py +0 -0
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/src/fastapi_authly/models/__init__.py +0 -0
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/src/fastapi_authly/models/user.py +0 -0
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/src/fastapi_authly/schemas/__init__.py +0 -0
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/src/fastapi_authly/schemas/user.py +0 -0
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/src/fastapi_authly/static/scalar/standalone.js +0 -0
- {fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/src/fastapi_authly/static/scalar/style.css +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastapi-authly
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: A modular authentication system for FastAPI with OAuth2, JWT, and password recovery
|
|
5
5
|
Project-URL: Homepage, https://github.com/yourusername/fastapi-auth-module
|
|
6
6
|
Project-URL: Documentation, https://yourusername.github.io/fastapi-auth-module/
|
|
@@ -25,6 +25,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
25
25
|
Classifier: Topic :: Internet :: WWW/HTTP :: Session
|
|
26
26
|
Classifier: Topic :: Security
|
|
27
27
|
Requires-Python: >=3.10
|
|
28
|
+
Requires-Dist: asyncpg>=0.31.0
|
|
28
29
|
Requires-Dist: emails>=0.6.0
|
|
29
30
|
Requires-Dist: fastapi>=0.100.0
|
|
30
31
|
Requires-Dist: jinja2>=3.0.0
|
|
@@ -35,6 +36,7 @@ Requires-Dist: pydantic[email]>=2.0.0
|
|
|
35
36
|
Requires-Dist: python-jose[cryptography]>=3.3.0
|
|
36
37
|
Requires-Dist: python-multipart>=0.0.6
|
|
37
38
|
Requires-Dist: tortoise-orm[psycopg]>=0.25.3
|
|
39
|
+
Requires-Dist: uvicorn>=0.40.0
|
|
38
40
|
Provides-Extra: docs
|
|
39
41
|
Requires-Dist: mkdocs-material>=9.0.0; extra == 'docs'
|
|
40
42
|
Requires-Dist: mkdocs>=1.5.0; extra == 'docs'
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""
|
|
2
|
+
正确的使用方式示例
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from fastapi import FastAPI
|
|
6
|
+
from tortoise.contrib.fastapi import register_tortoise
|
|
7
|
+
from fastapi_authly import (
|
|
8
|
+
AuthConfig,
|
|
9
|
+
AuthDependencyConfig,
|
|
10
|
+
create_auth_router,
|
|
11
|
+
JwtConfig,
|
|
12
|
+
setup_scalar_docs
|
|
13
|
+
)
|
|
14
|
+
from fastapi_authly.contrib.tortoise_pg import TortoiseUserRepository
|
|
15
|
+
|
|
16
|
+
app = FastAPI(title="FastAPI Authly 测试应用")
|
|
17
|
+
|
|
18
|
+
# 初始化 Tortoise + Postgres
|
|
19
|
+
register_tortoise(
|
|
20
|
+
app,
|
|
21
|
+
db_url="postgres://user:password@localhost:5432/testdb",
|
|
22
|
+
modules={"models": ["fastapi_authly.models.user"]},
|
|
23
|
+
generate_schemas=True,
|
|
24
|
+
add_exception_handlers=True,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
# 方式 1: 使用 JwtConfig 实例(推荐)
|
|
28
|
+
config = AuthConfig(
|
|
29
|
+
jwt=JwtConfig(
|
|
30
|
+
secret_key="your-secret-key-change-in-production",
|
|
31
|
+
algorithm="HS256",
|
|
32
|
+
access_token_expires_minutes=30,
|
|
33
|
+
refresh_token_expire_days=7,
|
|
34
|
+
),
|
|
35
|
+
router_prefix="/auth",
|
|
36
|
+
token_url="login",
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# 方式 2: 使用字典更新(也可以)
|
|
40
|
+
# config = AuthConfig(
|
|
41
|
+
# jwt={
|
|
42
|
+
# "secret_key": "your-secret-key-change-in-production",
|
|
43
|
+
# "algorithm": "HS256",
|
|
44
|
+
# "access_token_expires_minutes": 30,
|
|
45
|
+
# },
|
|
46
|
+
# router_prefix="/auth",
|
|
47
|
+
# token_url="login",
|
|
48
|
+
# )
|
|
49
|
+
|
|
50
|
+
# 方式 3: 使用默认配置,然后通过环境变量覆盖
|
|
51
|
+
# 设置环境变量: AUTH_JWT__SECRET_KEY=your-secret-key
|
|
52
|
+
# config = AuthConfig(
|
|
53
|
+
# router_prefix="/auth",
|
|
54
|
+
# token_url="login",
|
|
55
|
+
# )
|
|
56
|
+
|
|
57
|
+
deps = AuthDependencyConfig(
|
|
58
|
+
user_repository=TortoiseUserRepository(),
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# 注册认证路由
|
|
62
|
+
auth_router = create_auth_router(config=config, dependencies=deps)
|
|
63
|
+
app.include_router(auth_router)
|
|
64
|
+
|
|
65
|
+
# 设置 Scalar 文档
|
|
66
|
+
setup_scalar_docs(app, docs_url="/docs", static_url="/static")
|
|
67
|
+
|
|
68
|
+
@app.get("/")
|
|
69
|
+
async def root():
|
|
70
|
+
return {"message": "FastAPI Authly 测试应用已启动"}
|
|
71
|
+
|
|
72
|
+
if __name__ == "__main__":
|
|
73
|
+
import uvicorn
|
|
74
|
+
uvicorn.run(app, host="0.0.0.0", port=8000)
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "fastapi-authly"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.4"
|
|
8
8
|
description = "A modular authentication system for FastAPI with OAuth2, JWT, and password recovery"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "MIT"}
|
|
@@ -49,6 +49,8 @@ dependencies = [
|
|
|
49
49
|
"jinja2>=3.0.0",
|
|
50
50
|
"jwt>=1.3.1",
|
|
51
51
|
"tortoise-orm[psycopg]>=0.25.3",
|
|
52
|
+
"uvicorn>=0.40.0",
|
|
53
|
+
"asyncpg>=0.31.0",
|
|
52
54
|
]
|
|
53
55
|
|
|
54
56
|
[project.optional-dependencies]
|
|
@@ -7,21 +7,23 @@ This package provides a complete authentication solution with:
|
|
|
7
7
|
- Password recovery
|
|
8
8
|
- User management
|
|
9
9
|
- Modular and configurable design
|
|
10
|
+
- Built-in Scalar API documentation
|
|
10
11
|
"""
|
|
11
12
|
|
|
12
13
|
from .auth import AuthModule, create_auth_router
|
|
13
|
-
from .core import AuthConfig, AuthDependencyConfig, BcryptPasswordHasher, JWTTokenService
|
|
14
|
+
from .core import AuthConfig, AuthDependencyConfig, JwtConfig, BcryptPasswordHasher, JWTTokenService
|
|
14
15
|
from .docs import setup_scalar_docs
|
|
15
16
|
from .interfaces import Mailer, PasswordHasher, TokenService, UserRepository
|
|
16
17
|
from .schemas.user import UserBase, UserCreate, UserUpdate, UserPublic, Token, TokenData
|
|
17
18
|
|
|
18
|
-
__version__ = "0.1.
|
|
19
|
+
__version__ = "0.1.1"
|
|
19
20
|
|
|
20
21
|
__all__ = [
|
|
21
22
|
# Main classes
|
|
22
23
|
"AuthModule",
|
|
23
24
|
"AuthConfig",
|
|
24
25
|
"AuthDependencyConfig",
|
|
26
|
+
"JwtConfig",
|
|
25
27
|
|
|
26
28
|
# Core functions
|
|
27
29
|
"create_auth_router",
|
|
@@ -103,12 +103,14 @@ class AuthModule:
|
|
|
103
103
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
104
104
|
detail="Incorrect username or password",
|
|
105
105
|
)
|
|
106
|
-
|
|
106
|
+
|
|
107
|
+
hashed_password = getattr(user, "hashed_password", None) or ""
|
|
108
|
+
if not hashed_password or not self.password_hasher.verify(body.password, hashed_password):
|
|
107
109
|
raise HTTPException(
|
|
108
110
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
109
111
|
detail="Incorrect username or password",
|
|
110
112
|
)
|
|
111
|
-
|
|
113
|
+
|
|
112
114
|
if not user.is_active:
|
|
113
115
|
raise HTTPException(status_code=400, detail="Inactive user")
|
|
114
116
|
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
"""Core functionality for fastapi-authly."""
|
|
2
2
|
|
|
3
|
-
from .config import AuthConfig, AuthDependencyConfig
|
|
3
|
+
from .config import AuthConfig, AuthDependencyConfig, JwtConfig
|
|
4
4
|
from .security import BcryptPasswordHasher, JWTTokenService
|
|
5
5
|
|
|
6
6
|
__all__ = [
|
|
7
7
|
"AuthConfig",
|
|
8
8
|
"AuthDependencyConfig",
|
|
9
|
+
"JwtConfig",
|
|
9
10
|
"BcryptPasswordHasher",
|
|
10
11
|
"JWTTokenService",
|
|
11
12
|
]
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"""Configuration settings for fastapi-authly."""
|
|
2
2
|
|
|
3
|
-
from typing import List, Optional, Any
|
|
3
|
+
from typing import List, Optional, Any, Union
|
|
4
4
|
from datetime import timedelta
|
|
5
|
-
from pydantic import BaseModel, Field, ConfigDict
|
|
5
|
+
from pydantic import BaseModel, Field, ConfigDict, model_validator
|
|
6
6
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
7
7
|
|
|
8
8
|
class JwtConfig(BaseModel):
|
|
@@ -26,8 +26,6 @@ class JwtConfig(BaseModel):
|
|
|
26
26
|
return timedelta(days=self.refresh_token_expires_days)
|
|
27
27
|
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
29
|
class AuthConfig(BaseSettings):
|
|
32
30
|
"""
|
|
33
31
|
Runtime configuration loaded from environment variables with ``AUTH_`` prefix.
|
|
@@ -35,10 +33,11 @@ class AuthConfig(BaseSettings):
|
|
|
35
33
|
Host applications can still pass an ``AuthConfig`` instance directly;
|
|
36
34
|
in that case, the passed values override the environment values.
|
|
37
35
|
"""
|
|
38
|
-
jwt: JwtConfig = JwtConfig
|
|
36
|
+
jwt: Union[JwtConfig, dict] = Field(default_factory=JwtConfig)
|
|
39
37
|
|
|
40
38
|
router_prefix: str = "/auth"
|
|
41
39
|
router_tags: List[str] = Field(default_factory=lambda: ["authentication"])
|
|
40
|
+
token_url: str = "login" # OAuth2 token URL path
|
|
42
41
|
|
|
43
42
|
enable_password_recovery: bool = True
|
|
44
43
|
enable_user_registration: bool = True
|
|
@@ -58,6 +57,48 @@ class AuthConfig(BaseSettings):
|
|
|
58
57
|
env_prefix="AUTH_",
|
|
59
58
|
extra="ignore",
|
|
60
59
|
)
|
|
60
|
+
|
|
61
|
+
@model_validator(mode='after')
|
|
62
|
+
def _ensure_jwt_is_config(self):
|
|
63
|
+
"""确保 jwt 字段始终是 JwtConfig 实例"""
|
|
64
|
+
if isinstance(self.jwt, dict):
|
|
65
|
+
self.jwt = JwtConfig(**self.jwt)
|
|
66
|
+
elif not isinstance(self.jwt, JwtConfig):
|
|
67
|
+
self.jwt = JwtConfig()
|
|
68
|
+
return self
|
|
69
|
+
|
|
70
|
+
def _ensure_jwt_config(self) -> JwtConfig:
|
|
71
|
+
"""确保 jwt 是 JwtConfig 实例,如果不是则转换"""
|
|
72
|
+
if isinstance(self.jwt, dict):
|
|
73
|
+
self.jwt = JwtConfig(**self.jwt)
|
|
74
|
+
elif not isinstance(self.jwt, JwtConfig):
|
|
75
|
+
self.jwt = JwtConfig()
|
|
76
|
+
return self.jwt
|
|
77
|
+
|
|
78
|
+
# 属性访问器:直接访问 jwt 中的属性,兼容现有代码
|
|
79
|
+
@property
|
|
80
|
+
def secret_key(self) -> str:
|
|
81
|
+
"""JWT secret key"""
|
|
82
|
+
jwt_config = self._ensure_jwt_config()
|
|
83
|
+
return jwt_config.secret_key
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def algorithm(self) -> str:
|
|
87
|
+
"""JWT algorithm"""
|
|
88
|
+
jwt_config = self._ensure_jwt_config()
|
|
89
|
+
return jwt_config.algorithm
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def access_token_expire_minutes(self) -> Optional[int]:
|
|
93
|
+
"""Access token expiration in minutes (compatible with auth.py)"""
|
|
94
|
+
jwt_config = self._ensure_jwt_config()
|
|
95
|
+
return jwt_config.access_token_expires_minutes
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def refresh_token_expire_days(self) -> int:
|
|
99
|
+
"""Refresh token expiration in days"""
|
|
100
|
+
jwt_config = self._ensure_jwt_config()
|
|
101
|
+
return jwt_config.refresh_token_expires_days
|
|
61
102
|
|
|
62
103
|
_config = AuthConfig()
|
|
63
104
|
|
|
@@ -74,4 +115,6 @@ class AuthDependencyConfig(BaseModel):
|
|
|
74
115
|
token_service: Optional[Any] = None
|
|
75
116
|
mailer: Optional[Any] = None
|
|
76
117
|
# 可选:自定义 token 提取依赖(例如自定义 Bearer/JWT 解析)
|
|
118
|
+
# token_dependency: Optional[Any] = Noneptional[Any] = None
|
|
119
|
+
# 可选:自定义 token 提取依赖(例如自定义 Bearer/JWT 解析)
|
|
77
120
|
token_dependency: Optional[Any] = None
|
|
@@ -3,6 +3,7 @@ from typing import Any, Dict, Optional
|
|
|
3
3
|
|
|
4
4
|
from jose import JWTError, jwt
|
|
5
5
|
from passlib.context import CryptContext
|
|
6
|
+
from passlib.exc import UnknownHashError
|
|
6
7
|
|
|
7
8
|
from ..interfaces import PasswordHasher, TokenService
|
|
8
9
|
|
|
@@ -18,7 +19,13 @@ class BcryptPasswordHasher(PasswordHasher):
|
|
|
18
19
|
return self._ctx.hash(password)
|
|
19
20
|
|
|
20
21
|
def verify(self, password: str, hashed: str) -> bool:
|
|
21
|
-
|
|
22
|
+
if not hashed:
|
|
23
|
+
return False
|
|
24
|
+
try:
|
|
25
|
+
return self._ctx.verify(password, hashed)
|
|
26
|
+
except UnknownHashError:
|
|
27
|
+
# 如果哈希格式无法识别,返回 False
|
|
28
|
+
return False
|
|
22
29
|
|
|
23
30
|
|
|
24
31
|
class JWTTokenService(TokenService):
|
|
@@ -21,6 +21,7 @@ def setup_scalar_docs(
|
|
|
21
21
|
static_url: str = "/static",
|
|
22
22
|
title: Optional[str] = None,
|
|
23
23
|
openapi_url: Optional[str] = None,
|
|
24
|
+
favicon_url: Optional[str] = None,
|
|
24
25
|
) -> None:
|
|
25
26
|
"""
|
|
26
27
|
为 FastAPI 应用设置 Scalar API 文档
|
|
@@ -31,6 +32,8 @@ def setup_scalar_docs(
|
|
|
31
32
|
static_url: 静态文件 URL 前缀,默认为 "/static"
|
|
32
33
|
title: 文档标题,默认使用 app.title
|
|
33
34
|
openapi_url: OpenAPI schema URL,默认使用 app.openapi_url
|
|
35
|
+
favicon_url: Favicon 图标 URL,默认为 "{static_url}/favicon.svg"
|
|
36
|
+
如果设置为 None,将尝试使用默认路径
|
|
34
37
|
"""
|
|
35
38
|
static_dir = get_static_dir()
|
|
36
39
|
|
|
@@ -42,6 +45,22 @@ def setup_scalar_docs(
|
|
|
42
45
|
doc_title = title or app.title
|
|
43
46
|
api_url = openapi_url or app.openapi_url
|
|
44
47
|
|
|
48
|
+
if favicon_url is None:
|
|
49
|
+
static_dir = get_static_dir()
|
|
50
|
+
# 优先尝试 SVG,然后是 ICO
|
|
51
|
+
if os.path.exists(static_dir / "favicon.svg"):
|
|
52
|
+
favicon_url = f"{static_url}/favicon.svg"
|
|
53
|
+
else:
|
|
54
|
+
favicon_url = f"{static_url}/favicon.ico"
|
|
55
|
+
|
|
56
|
+
favicon_type = "image/x-icon" # 默认 ICO
|
|
57
|
+
if favicon_url.lower().endswith(".svg"):
|
|
58
|
+
favicon_type = "image/svg+xml"
|
|
59
|
+
elif favicon_url.lower().endswith(".png"):
|
|
60
|
+
favicon_type = "image/png"
|
|
61
|
+
elif favicon_url.lower().endswith(".ico"):
|
|
62
|
+
favicon_type = "image/x-icon"
|
|
63
|
+
|
|
45
64
|
# 创建文档路由
|
|
46
65
|
@app.get(docs_url, include_in_schema=False)
|
|
47
66
|
async def scalar_html():
|
|
@@ -52,6 +71,7 @@ def setup_scalar_docs(
|
|
|
52
71
|
<title>{doc_title}</title>
|
|
53
72
|
<meta charset="utf-8"/>
|
|
54
73
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
74
|
+
<link rel="icon" type="{favicon_type}" href="{favicon_url}">
|
|
55
75
|
<link rel="stylesheet" href="{static_url}/scalar/style.css">
|
|
56
76
|
</head>
|
|
57
77
|
<body>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1769412556021" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5147" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M24.856135 296.081347L490.69745 507.422478a51.196979 51.196979 0 0 0 42.595887 0l465.841316-211.341131c33.073249-15.000715 33.073249-65.020164 0-80.020879L533.293337 4.668141a51.196979 51.196979 0 0 0-42.595887 0l-465.892512 211.289933c-33.073249 15.051912-33.073249 65.020164 0 80.072076z m974.278518 176.578382l-116.217144-52.681692-323.20653 146.525755a114.732431 114.732431 0 0 1-95.43117 0L141.022081 419.978037 24.804938 472.659729c-33.073249 15.000715-33.073249 65.020164 0 79.969682l465.892512 211.18754c13.618397 6.143638 28.97749 6.143638 42.595887 0l465.841316-211.18754c33.073249-15.000715 33.073249-65.020164 0-79.969682z m0 255.575321l-115.705174-52.476904-323.7185 146.730543a114.732431 114.732431 0 0 1-95.43117 0l-323.667304-146.730543-115.75637 52.476904c-33.124446 15.000715-33.124446 65.020164 0 79.969682l465.841315 211.18754c13.618397 6.143638 28.97749 6.143638 42.595887 0l465.841316-211.18754c33.073249-15.000715 33.073249-65.020164 0-79.969682z" fill="#03CDC1" p-id="5148"></path></svg>
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""
|
|
2
|
+
正确的使用方式示例
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from fastapi import FastAPI
|
|
6
|
+
from tortoise.contrib.fastapi import register_tortoise
|
|
7
|
+
from src.fastapi_authly import (
|
|
8
|
+
AuthConfig,
|
|
9
|
+
AuthDependencyConfig,
|
|
10
|
+
create_auth_router,
|
|
11
|
+
JwtConfig,
|
|
12
|
+
setup_scalar_docs
|
|
13
|
+
)
|
|
14
|
+
from src.fastapi_authly.contrib.tortoise_pg import TortoiseUserRepository
|
|
15
|
+
|
|
16
|
+
app = FastAPI(title="FastAPI Authly 测试应用", docs_url="/old_docs",)
|
|
17
|
+
|
|
18
|
+
# 初始化 Tortoise + Postgres
|
|
19
|
+
register_tortoise(
|
|
20
|
+
app,
|
|
21
|
+
db_url="postgres://postgres:Dnect2016@192.168.0.81:5432/testdb",
|
|
22
|
+
modules={"models": ["src.fastapi_authly.models.user"]},
|
|
23
|
+
generate_schemas=True,
|
|
24
|
+
add_exception_handlers=True,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
# 方式 1: 使用 JwtConfig 实例(推荐)
|
|
28
|
+
config = AuthConfig(
|
|
29
|
+
jwt=JwtConfig(
|
|
30
|
+
secret_key="django-insecure-7qi2d#57gzg7br&sx-1n9c16)kf979m)jg+%vu^z80ti_#&d*_",
|
|
31
|
+
algorithm="HS256",
|
|
32
|
+
access_token_expires_minutes=30,
|
|
33
|
+
refresh_token_expires_days=7,
|
|
34
|
+
),
|
|
35
|
+
router_prefix="/auth",
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# 方式 2: 使用字典更新(也可以)
|
|
39
|
+
# config = AuthConfig(
|
|
40
|
+
# jwt={
|
|
41
|
+
# "secret_key": "your-secret-key-change-in-production",
|
|
42
|
+
# "algorithm": "HS256",
|
|
43
|
+
# "access_token_expires_minutes": 30,
|
|
44
|
+
# },
|
|
45
|
+
# router_prefix="/auth",
|
|
46
|
+
# token_url="login",
|
|
47
|
+
# )
|
|
48
|
+
|
|
49
|
+
# 方式 3: 使用默认配置,然后通过环境变量覆盖
|
|
50
|
+
# 设置环境变量: AUTH_JWT__SECRET_KEY=your-secret-key
|
|
51
|
+
# config = AuthConfig(
|
|
52
|
+
# router_prefix="/auth",
|
|
53
|
+
# token_url="login",
|
|
54
|
+
# )
|
|
55
|
+
|
|
56
|
+
deps = AuthDependencyConfig(
|
|
57
|
+
user_repository=TortoiseUserRepository(),
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# 注册认证路由
|
|
61
|
+
auth_router = create_auth_router(config=config, dependencies=deps)
|
|
62
|
+
app.include_router(auth_router)
|
|
63
|
+
|
|
64
|
+
# 设置 Scalar 文档
|
|
65
|
+
setup_scalar_docs(app, docs_url="/docs", static_url="/static")
|
|
66
|
+
|
|
67
|
+
@app.get("/")
|
|
68
|
+
async def root():
|
|
69
|
+
return {"message": "FastAPI Authly 测试应用已启动"}
|
|
70
|
+
|
|
71
|
+
if __name__ == "__main__":
|
|
72
|
+
import uvicorn
|
|
73
|
+
uvicorn.run(app, host="0.0.0.0", port=8001)
|
|
@@ -625,9 +625,10 @@ wheels = [
|
|
|
625
625
|
|
|
626
626
|
[[package]]
|
|
627
627
|
name = "fastapi-authly"
|
|
628
|
-
version = "0.1.
|
|
628
|
+
version = "0.1.3"
|
|
629
629
|
source = { editable = "." }
|
|
630
630
|
dependencies = [
|
|
631
|
+
{ name = "asyncpg" },
|
|
631
632
|
{ name = "emails" },
|
|
632
633
|
{ name = "fastapi" },
|
|
633
634
|
{ name = "jinja2" },
|
|
@@ -638,6 +639,7 @@ dependencies = [
|
|
|
638
639
|
{ name = "python-jose", extra = ["cryptography"] },
|
|
639
640
|
{ name = "python-multipart" },
|
|
640
641
|
{ name = "tortoise-orm", extra = ["psycopg"] },
|
|
642
|
+
{ name = "uvicorn" },
|
|
641
643
|
]
|
|
642
644
|
|
|
643
645
|
[package.optional-dependencies]
|
|
@@ -662,6 +664,7 @@ tortoise = [
|
|
|
662
664
|
[package.metadata]
|
|
663
665
|
requires-dist = [
|
|
664
666
|
{ name = "alembic", marker = "extra == 'sqlalchemy'", specifier = ">=1.12.0" },
|
|
667
|
+
{ name = "asyncpg", specifier = ">=0.31.0" },
|
|
665
668
|
{ name = "emails", specifier = ">=0.6.0" },
|
|
666
669
|
{ name = "faker", marker = "extra == 'test'", specifier = ">=20.0.0" },
|
|
667
670
|
{ name = "fastapi", specifier = ">=0.100.0" },
|
|
@@ -680,6 +683,7 @@ requires-dist = [
|
|
|
680
683
|
{ name = "sqlalchemy", marker = "extra == 'sqlalchemy'", specifier = ">=2.0.0" },
|
|
681
684
|
{ name = "tortoise-orm", extras = ["asyncpg"], marker = "extra == 'tortoise'", specifier = ">=0.20.0" },
|
|
682
685
|
{ name = "tortoise-orm", extras = ["psycopg"], specifier = ">=0.25.3" },
|
|
686
|
+
{ name = "uvicorn", specifier = ">=0.40.0" },
|
|
683
687
|
]
|
|
684
688
|
provides-extras = ["tortoise", "sqlalchemy", "docs", "test"]
|
|
685
689
|
|
|
@@ -1908,6 +1912,20 @@ wheels = [
|
|
|
1908
1912
|
{ url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" },
|
|
1909
1913
|
]
|
|
1910
1914
|
|
|
1915
|
+
[[package]]
|
|
1916
|
+
name = "uvicorn"
|
|
1917
|
+
version = "0.40.0"
|
|
1918
|
+
source = { registry = "https://pypi.org/simple" }
|
|
1919
|
+
dependencies = [
|
|
1920
|
+
{ name = "click" },
|
|
1921
|
+
{ name = "h11" },
|
|
1922
|
+
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
|
|
1923
|
+
]
|
|
1924
|
+
sdist = { url = "https://files.pythonhosted.org/packages/c3/d1/8f3c683c9561a4e6689dd3b1d345c815f10f86acd044ee1fb9a4dcd0b8c5/uvicorn-0.40.0.tar.gz", hash = "sha256:839676675e87e73694518b5574fd0f24c9d97b46bea16df7b8c05ea1a51071ea", size = 81761, upload-time = "2025-12-21T14:16:22.45Z" }
|
|
1925
|
+
wheels = [
|
|
1926
|
+
{ url = "https://files.pythonhosted.org/packages/3d/d8/2083a1daa7439a66f3a48589a57d576aa117726762618f6bb09fe3798796/uvicorn-0.40.0-py3-none-any.whl", hash = "sha256:c6c8f55bc8bf13eb6fa9ff87ad62308bbbc33d0b67f84293151efe87e0d5f2ee", size = 68502, upload-time = "2025-12-21T14:16:21.041Z" },
|
|
1927
|
+
]
|
|
1928
|
+
|
|
1911
1929
|
[[package]]
|
|
1912
1930
|
name = "watchdog"
|
|
1913
1931
|
version = "6.0.0"
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
# Scalar 文档功能集成说明
|
|
2
|
-
|
|
3
|
-
## 改动概述
|
|
4
|
-
|
|
5
|
-
已将 Scalar API 文档功能集成到 `fastapi-authly` 包中,包含所有必要的静态资源文件。用户只需导入并使用 `setup_scalar_docs` 函数即可启用文档功能,无需手动管理静态文件。
|
|
6
|
-
|
|
7
|
-
## 新增文件
|
|
8
|
-
|
|
9
|
-
1. **`src/fastapi_authly/docs.py`**: 文档功能模块
|
|
10
|
-
- `setup_scalar_docs()`: 主要函数,用于设置 Scalar 文档
|
|
11
|
-
- `get_static_dir()`: 获取包内静态文件目录路径
|
|
12
|
-
|
|
13
|
-
2. **`src/fastapi_authly/static/scalar/`**: 静态资源目录
|
|
14
|
-
- `standalone.js`: Scalar JavaScript 文件
|
|
15
|
-
- `style.css`: Scalar CSS 样式文件
|
|
16
|
-
|
|
17
|
-
3. **示例文件**:
|
|
18
|
-
- `examples/use_scalar_docs.py`: 基本使用示例
|
|
19
|
-
- `examples/complete_example.py`: 完整使用示例
|
|
20
|
-
- `docs/SCALAR_DOCS_USAGE.md`: 详细使用文档
|
|
21
|
-
|
|
22
|
-
## 修改文件
|
|
23
|
-
|
|
24
|
-
1. **`src/fastapi_authly/__init__.py`**:
|
|
25
|
-
- 添加 `setup_scalar_docs` 的导入和导出
|
|
26
|
-
|
|
27
|
-
2. **`README.md` 和 `README.zh.md`**:
|
|
28
|
-
- 添加 Scalar 文档功能的使用说明
|
|
29
|
-
|
|
30
|
-
## 使用方法
|
|
31
|
-
|
|
32
|
-
### 基本使用
|
|
33
|
-
|
|
34
|
-
```python
|
|
35
|
-
from fastapi import FastAPI
|
|
36
|
-
from fastapi_authly import setup_scalar_docs
|
|
37
|
-
|
|
38
|
-
app = FastAPI(title="My API")
|
|
39
|
-
setup_scalar_docs(app)
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
### 自定义配置
|
|
43
|
-
|
|
44
|
-
```python
|
|
45
|
-
setup_scalar_docs(
|
|
46
|
-
app,
|
|
47
|
-
docs_url="/api-docs",
|
|
48
|
-
static_url="/assets",
|
|
49
|
-
title="Custom API Docs",
|
|
50
|
-
openapi_url="/openapi.json"
|
|
51
|
-
)
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
## 优势
|
|
55
|
-
|
|
56
|
-
1. **零配置**: 无需手动复制静态文件
|
|
57
|
-
2. **可移植**: 安装包后即可使用
|
|
58
|
-
3. **简单**: 一行代码启用
|
|
59
|
-
4. **灵活**: 支持自定义 URL 和配置
|
|
60
|
-
|
|
61
|
-
## 构建和发布
|
|
62
|
-
|
|
63
|
-
静态文件会自动包含在构建的包中,因为它们在 `src/fastapi_authly/static/` 目录下,属于包的一部分。
|
|
64
|
-
|
|
65
|
-
构建和发布流程不变:
|
|
66
|
-
|
|
67
|
-
```bash
|
|
68
|
-
uv build
|
|
69
|
-
uv publish --token your-token
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
## 测试
|
|
73
|
-
|
|
74
|
-
安装包后,可以这样测试:
|
|
75
|
-
|
|
76
|
-
```python
|
|
77
|
-
from fastapi import FastAPI
|
|
78
|
-
from fastapi_authly import setup_scalar_docs
|
|
79
|
-
|
|
80
|
-
app = FastAPI(title="Test API")
|
|
81
|
-
setup_scalar_docs(app)
|
|
82
|
-
|
|
83
|
-
# 运行应用后,访问 http://localhost:8000/docs 即可看到文档
|
|
84
|
-
```
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
# Scalar API 文档功能使用指南
|
|
2
|
-
|
|
3
|
-
## 概述
|
|
4
|
-
|
|
5
|
-
`fastapi-authly` 现在内置了 Scalar API 文档支持,包含所有必要的静态资源文件。你无需手动复制静态文件,只需一行代码即可启用美观的 API 文档。
|
|
6
|
-
|
|
7
|
-
## 快速开始
|
|
8
|
-
|
|
9
|
-
### 基本使用
|
|
10
|
-
|
|
11
|
-
```python
|
|
12
|
-
from fastapi import FastAPI
|
|
13
|
-
from fastapi_authly import setup_scalar_docs
|
|
14
|
-
|
|
15
|
-
app = FastAPI(title="My API")
|
|
16
|
-
|
|
17
|
-
# 一行代码启用 Scalar 文档
|
|
18
|
-
setup_scalar_docs(app)
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
这将会:
|
|
22
|
-
- 自动挂载静态文件到 `/static`
|
|
23
|
-
- 创建文档页面到 `/docs`
|
|
24
|
-
- 使用应用的 `title` 和 `openapi_url`
|
|
25
|
-
|
|
26
|
-
### 完整示例
|
|
27
|
-
|
|
28
|
-
```python
|
|
29
|
-
from fastapi import FastAPI
|
|
30
|
-
from fastapi_authly import setup_scalar_docs, create_auth_router, AuthConfig, AuthDependencyConfig
|
|
31
|
-
from fastapi_authly.contrib.tortoise_pg import TortoiseUserRepository
|
|
32
|
-
from tortoise.contrib.fastapi import register_tortoise
|
|
33
|
-
|
|
34
|
-
app = FastAPI(
|
|
35
|
-
title="Complaint API",
|
|
36
|
-
description="Complaint Data Streaming API",
|
|
37
|
-
version="2.0.1",
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
# 初始化数据库
|
|
41
|
-
register_tortoise(
|
|
42
|
-
app,
|
|
43
|
-
db_url="postgres://user:password@localhost:5432/mydb",
|
|
44
|
-
modules={"models": ["fastapi_authly.models.user"]},
|
|
45
|
-
generate_schemas=True,
|
|
46
|
-
add_exception_handlers=True,
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
# 配置认证路由
|
|
50
|
-
config = AuthConfig()
|
|
51
|
-
deps = AuthDependencyConfig(user_repository=TortoiseUserRepository())
|
|
52
|
-
auth_router = create_auth_router(config=config, dependencies=deps)
|
|
53
|
-
app.include_router(auth_router)
|
|
54
|
-
|
|
55
|
-
# 设置 Scalar 文档
|
|
56
|
-
setup_scalar_docs(app, docs_url="/docs", static_url="/static")
|
|
57
|
-
|
|
58
|
-
if __name__ == "__main__":
|
|
59
|
-
import uvicorn
|
|
60
|
-
uvicorn.run(app, host="0.0.0.0", port=8000)
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
## 自定义配置
|
|
64
|
-
|
|
65
|
-
```python
|
|
66
|
-
setup_scalar_docs(
|
|
67
|
-
app,
|
|
68
|
-
docs_url="/api-docs", # 自定义文档 URL
|
|
69
|
-
static_url="/assets", # 自定义静态文件前缀
|
|
70
|
-
title="Custom API Docs", # 自定义标题
|
|
71
|
-
openapi_url="/openapi.json" # 自定义 OpenAPI schema URL
|
|
72
|
-
)
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
## 参数说明
|
|
76
|
-
|
|
77
|
-
- `app` (必需): FastAPI 应用实例
|
|
78
|
-
- `docs_url` (可选): 文档页面 URL,默认为 `"/docs"`
|
|
79
|
-
- `static_url` (可选): 静态文件 URL 前缀,默认为 `"/static"`
|
|
80
|
-
- `title` (可选): 文档标题,默认使用 `app.title`
|
|
81
|
-
- `openapi_url` (可选): OpenAPI schema URL,默认使用 `app.openapi_url`
|
|
82
|
-
|
|
83
|
-
## 注意事项
|
|
84
|
-
|
|
85
|
-
1. **静态文件位置**: 静态文件已经打包在 `fastapi_authly` 包内,无需手动复制
|
|
86
|
-
2. **URL 冲突**: 确保 `docs_url` 和 `static_url` 不与现有路由冲突
|
|
87
|
-
3. **OpenAPI Schema**: 确保 FastAPI 应用已启用 OpenAPI schema(默认启用)
|
|
88
|
-
|
|
89
|
-
## 迁移指南
|
|
90
|
-
|
|
91
|
-
如果你之前手动配置了 Scalar 文档,可以按以下步骤迁移:
|
|
92
|
-
|
|
93
|
-
### 之前的方式
|
|
94
|
-
|
|
95
|
-
```python
|
|
96
|
-
app.mount("/static", StaticFiles(directory="static"), name="static")
|
|
97
|
-
|
|
98
|
-
@app.get("/docs", include_in_schema=False)
|
|
99
|
-
async def scalar_html(_app):
|
|
100
|
-
html_content = f"""
|
|
101
|
-
<!DOCTYPE html>
|
|
102
|
-
...
|
|
103
|
-
"""
|
|
104
|
-
return HTMLResponse(content=html_content)
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
### 现在的方式
|
|
108
|
-
|
|
109
|
-
```python
|
|
110
|
-
from fastapi_authly import setup_scalar_docs
|
|
111
|
-
|
|
112
|
-
# 一行代码替代上面的所有配置
|
|
113
|
-
setup_scalar_docs(app, docs_url="/docs", static_url="/static")
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
## 优势
|
|
117
|
-
|
|
118
|
-
1. **无需手动管理静态文件**: 所有静态资源已打包在包内
|
|
119
|
-
2. **简单易用**: 一行代码即可启用
|
|
120
|
-
3. **可移植**: 其他项目安装包后即可使用,无需额外配置
|
|
121
|
-
4. **可定制**: 支持自定义 URL 和标题
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
完整示例:展示如何像原项目一样使用 fastapi-authly 的文档功能
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from fastapi import FastAPI
|
|
6
|
-
from fastapi_authly import setup_scalar_docs, create_auth_router, AuthConfig, AuthDependencyConfig
|
|
7
|
-
from fastapi_authly.contrib.tortoise_pg import TortoiseUserRepository
|
|
8
|
-
from tortoise.contrib.fastapi import register_tortoise, tortoise_exception_handlers
|
|
9
|
-
from contextlib import asynccontextmanager
|
|
10
|
-
|
|
11
|
-
# 生命周期管理
|
|
12
|
-
@asynccontextmanager
|
|
13
|
-
async def lifespan(app: FastAPI):
|
|
14
|
-
# 启动时执行
|
|
15
|
-
yield
|
|
16
|
-
# 关闭时执行
|
|
17
|
-
|
|
18
|
-
# 创建 FastAPI 应用
|
|
19
|
-
app = FastAPI(
|
|
20
|
-
title="Complaint API",
|
|
21
|
-
description="Complaint Data Streaming API",
|
|
22
|
-
version="2.0.1",
|
|
23
|
-
exception_handlers=tortoise_exception_handlers(),
|
|
24
|
-
lifespan=lifespan,
|
|
25
|
-
docs_url="/old_docs", # 保留旧的 docs,但使用 Scalar
|
|
26
|
-
)
|
|
27
|
-
|
|
28
|
-
# 初始化数据库
|
|
29
|
-
register_tortoise(
|
|
30
|
-
app,
|
|
31
|
-
db_url="postgres://user:password@localhost:5432/mydb",
|
|
32
|
-
modules={"models": ["fastapi_authly.models.user"]},
|
|
33
|
-
generate_schemas=True,
|
|
34
|
-
add_exception_handlers=True,
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
# 配置认证路由
|
|
38
|
-
config = AuthConfig()
|
|
39
|
-
deps = AuthDependencyConfig(user_repository=TortoiseUserRepository())
|
|
40
|
-
auth_router = create_auth_router(config=config, dependencies=deps)
|
|
41
|
-
app.include_router(auth_router)
|
|
42
|
-
|
|
43
|
-
# 设置 Scalar 文档
|
|
44
|
-
# 这会自动挂载静态文件并创建文档页面
|
|
45
|
-
setup_scalar_docs(
|
|
46
|
-
app,
|
|
47
|
-
docs_url="/docs", # 文档页面 URL
|
|
48
|
-
static_url="/static", # 静态文件 URL 前缀(与原来的保持一致)
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
# 其他 API 路由
|
|
52
|
-
@app.get("/")
|
|
53
|
-
async def root():
|
|
54
|
-
return {"message": "API is running"}
|
|
55
|
-
|
|
56
|
-
if __name__ == "__main__":
|
|
57
|
-
import uvicorn
|
|
58
|
-
uvicorn.run(
|
|
59
|
-
app,
|
|
60
|
-
host="0.0.0.0",
|
|
61
|
-
port=3040,
|
|
62
|
-
loop="uvloop",
|
|
63
|
-
)
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
使用示例:如何在项目中使用 fastapi-authly 的 Scalar 文档功能
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from fastapi import FastAPI
|
|
6
|
-
from fastapi_authly import setup_scalar_docs, create_auth_router, AuthConfig, AuthDependencyConfig
|
|
7
|
-
from fastapi_authly.contrib.tortoise_pg import TortoiseUserRepository
|
|
8
|
-
from tortoise.contrib.fastapi import register_tortoise
|
|
9
|
-
|
|
10
|
-
# 创建 FastAPI 应用
|
|
11
|
-
app = FastAPI(
|
|
12
|
-
title="My API",
|
|
13
|
-
description="My API with Scalar Documentation",
|
|
14
|
-
version="1.0.0",
|
|
15
|
-
)
|
|
16
|
-
|
|
17
|
-
# 配置认证路由(示例)
|
|
18
|
-
config = AuthConfig()
|
|
19
|
-
deps = AuthDependencyConfig(user_repository=TortoiseUserRepository())
|
|
20
|
-
auth_router = create_auth_router(config=config, dependencies=deps)
|
|
21
|
-
app.include_router(auth_router)
|
|
22
|
-
|
|
23
|
-
# 设置 Scalar 文档
|
|
24
|
-
# 这会自动:
|
|
25
|
-
# 1. 挂载静态文件到 /static
|
|
26
|
-
# 2. 创建文档页面到 /docs
|
|
27
|
-
setup_scalar_docs(
|
|
28
|
-
app,
|
|
29
|
-
docs_url="/docs", # 文档页面 URL
|
|
30
|
-
static_url="/static", # 静态文件 URL 前缀
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
# 或者自定义配置
|
|
34
|
-
# setup_scalar_docs(
|
|
35
|
-
# app,
|
|
36
|
-
# docs_url="/api-docs", # 自定义文档 URL
|
|
37
|
-
# static_url="/assets", # 自定义静态文件前缀
|
|
38
|
-
# title="Custom API Docs", # 自定义标题
|
|
39
|
-
# openapi_url="/openapi.json", # 自定义 OpenAPI schema URL
|
|
40
|
-
# )
|
|
41
|
-
|
|
42
|
-
# 初始化数据库(示例)
|
|
43
|
-
register_tortoise(
|
|
44
|
-
app,
|
|
45
|
-
db_url="postgres://user:password@localhost:5432/mydb",
|
|
46
|
-
modules={"models": ["fastapi_authly.models.user"]},
|
|
47
|
-
generate_schemas=True,
|
|
48
|
-
add_exception_handlers=True,
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
if __name__ == "__main__":
|
|
52
|
-
import uvicorn
|
|
53
|
-
uvicorn.run(app, host="0.0.0.0", port=8000)
|
fastapi_authly-0.1.2/test.py
DELETED
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
"""
|
|
3
|
-
题 1(入门)
|
|
4
|
-
给定 nums = [1, 2, 3, 4, 5],用 map 得到每个元素的平方,输出为列表。
|
|
5
|
-
|
|
6
|
-
"""
|
|
7
|
-
numbers = [1,2,3,4,5,6,7,8,9]
|
|
8
|
-
content = map(lambda x: x**2, numbers)
|
|
9
|
-
print(list(content))
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
"""
|
|
13
|
-
给定 words = [" Alice ", "BOB", "cHaRlIe "]
|
|
14
|
-
用 map 把每个元素转换成:去掉两端空格 + 全部小写。
|
|
15
|
-
"""
|
|
16
|
-
|
|
17
|
-
a = 'Word'
|
|
18
|
-
words = [" Alice ", "BOB", "cHaRlIe "]
|
|
19
|
-
|
|
20
|
-
new_words = map(lambda x: x.strip().lower(), words)
|
|
21
|
-
|
|
22
|
-
print(list(new_words))
|
|
23
|
-
|
|
24
|
-
"""
|
|
25
|
-
### 题 3(类型转换 + 容错)
|
|
26
|
-
给定 `raw = ["1", "2", "x", "3", ""]`
|
|
27
|
-
实现一个转换函数:能转成 int 的转 int,不能转的返回 `None`。
|
|
28
|
-
用 `map` 得到结果列表。
|
|
29
|
-
"""
|
|
30
|
-
|
|
31
|
-
raw = ["1", "2", "x", "3", ""]
|
|
32
|
-
|
|
33
|
-
def change(x):
|
|
34
|
-
try:
|
|
35
|
-
return int(x)
|
|
36
|
-
except ValueError:
|
|
37
|
-
return None
|
|
38
|
-
|
|
39
|
-
content = map(change, raw)
|
|
40
|
-
|
|
41
|
-
print(list(content))
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"""
|
|
45
|
-
### 题 4(多个 iterable)
|
|
46
|
-
给定:
|
|
47
|
-
|
|
48
|
-
```python
|
|
49
|
-
a = [1, 2, 3, 4]
|
|
50
|
-
b = [10, 20, 30]
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
用 `map` 生成 `a[i] + b[i]` 的列表,并解释为什么结果长度是多少。
|
|
54
|
-
"""
|
|
55
|
-
|
|
56
|
-
a = [1, 2, 3, 4]
|
|
57
|
-
b = [10, 20, 30]
|
|
58
|
-
|
|
59
|
-
def _add(x,y):
|
|
60
|
-
print(x,y)
|
|
61
|
-
|
|
62
|
-
content = map(lambda x,y:x+y, a, b)
|
|
63
|
-
|
|
64
|
-
print(list(content))
|
|
65
|
-
# 长度为最短的那个可迭代对象的长度,因为灰爆出stop什么来着
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
"""
|
|
69
|
-
### 题 5(嵌套数据处理)
|
|
70
|
-
给定:
|
|
71
|
-
|
|
72
|
-
```python
|
|
73
|
-
records = [
|
|
74
|
-
{"name": "alice", "score": "98"},
|
|
75
|
-
{"name": "bob", "score": "87"},
|
|
76
|
-
{"name": "cathy", "score": "100"},
|
|
77
|
-
]
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
用 `map` 输出一个列表:每个元素是 `(NAME_UPPER, score_int)`。
|
|
81
|
-
"""
|
|
82
|
-
|
|
83
|
-
records = [
|
|
84
|
-
{"name": "alice", "score": "98"},
|
|
85
|
-
{"name": "bob", "score": "87"},
|
|
86
|
-
{"name": "cathy", "score": "100"},
|
|
87
|
-
]
|
|
88
|
-
|
|
89
|
-
content = map(lambda x: (x["name"].upper(), x["score"]), records)
|
|
90
|
-
|
|
91
|
-
print(list(content))
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
"""
|
|
95
|
-
### 题 6(惰性验证题)
|
|
96
|
-
写一段最短代码证明:`map` 不会立刻执行函数,而是在迭代时执行。
|
|
97
|
-
要求:必须能从输出看出来“什么时候调用”。
|
|
98
|
-
"""
|
|
99
|
-
|
|
100
|
-
numbers = [1,2,3,4,5,6,7,8,9]
|
|
101
|
-
|
|
102
|
-
content = map(lambda x: print(f"当前{x}"), numbers)
|
|
103
|
-
|
|
104
|
-
print(next(content))
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
"""
|
|
108
|
-
### 题 7(组合题:map + filter)
|
|
109
|
-
给定 `nums = list(range(-5, 6))`
|
|
110
|
-
用 **filter 先筛掉负数**,再用 **map 转平方**,输出列表。
|
|
111
|
-
(要求用函数式写法,不用列表推导式。)
|
|
112
|
-
"""
|
|
113
|
-
nums = list(range(-5, 6))
|
|
114
|
-
|
|
115
|
-
filtered = filter(lambda x: x > 0, nums)
|
|
116
|
-
|
|
117
|
-
new_nums = map(lambda x: x**2, filtered)
|
|
118
|
-
print(list(new_nums))
|
|
119
|
-
|
|
120
|
-
"""
|
|
121
|
-
### 题 8(进阶:并行处理 + 缺失值)
|
|
122
|
-
给定:
|
|
123
|
-
|
|
124
|
-
```python
|
|
125
|
-
names = ["alice", "bob", "cathy", "dan"]
|
|
126
|
-
scores = ["100", "", "87", "x"]
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
目标输出一个列表:`["Alice:100", "Bob:NA", "Cathy:87", "Dan:NA"]`
|
|
130
|
-
规则:分数能转 int 就用该数字,否则用 `"NA"`。
|
|
131
|
-
要求:用 `map`,并尽量把逻辑放进一个清晰的转换函数里。
|
|
132
|
-
|
|
133
|
-
"""
|
|
134
|
-
names = ["alice", "bob", "cathy", "dan"]
|
|
135
|
-
scores = ["100", "", "87", "x"]
|
|
136
|
-
|
|
137
|
-
def chang(name,score):
|
|
138
|
-
try:
|
|
139
|
-
score = int(score)
|
|
140
|
-
return f"{name}:{score}"
|
|
141
|
-
except ValueError:
|
|
142
|
-
return f"{name}:NA"
|
|
143
|
-
|
|
144
|
-
content = map(chang, names, scores)
|
|
145
|
-
|
|
146
|
-
print(list(content))
|
|
147
|
-
|
|
148
|
-
print(callable(chang))
|
|
149
|
-
|
|
150
|
-
"""
|
|
151
|
-
### 题 9(较难:实现你自己的 map)
|
|
152
|
-
实现一个函数 `my_map(func, iterable)`,返回一个**生成器**,行为尽量像 `map`(惰性、一次产出一个)。
|
|
153
|
-
然后用它跑一下题 1 的例子验证。
|
|
154
|
-
"""
|
|
155
|
-
|
|
156
|
-
from typing import Callable
|
|
157
|
-
|
|
158
|
-
def my_map(func: Callable, iterable):
|
|
159
|
-
if not callable(func):
|
|
160
|
-
raise "func 应该是一个函数"
|
|
161
|
-
|
|
162
|
-
# todo 应该增加iterable 的判断,判断他是一个可迭代的对象
|
|
163
|
-
|
|
164
|
-
# todo 应该增加可是传递多个可迭代对象,但是我不知道怎么做
|
|
165
|
-
for item in iterable:
|
|
166
|
-
yield func(item)
|
|
167
|
-
|
|
168
|
-
numbers = [1,2,3,4,5,6,7,8,9]
|
|
169
|
-
|
|
170
|
-
content = my_map(lambda x: x**2, numbers)
|
|
171
|
-
|
|
172
|
-
print(next(content))
|
|
173
|
-
|
|
174
|
-
print(list(content))
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
print(map.__doc__)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/htmlcov/z_ddd01122054512b0_tortoise_pg_py.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_authly-0.1.2 → fastapi_authly-0.1.4}/src/fastapi_authly/static/scalar/standalone.js
RENAMED
|
File without changes
|
|
File without changes
|