fastapi-authly 0.1.26__tar.gz → 0.1.28__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.
Files changed (56) hide show
  1. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/PKG-INFO +1 -1
  2. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/pyproject.toml +1 -1
  3. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/src/fastapi_authly/__about__.py +1 -1
  4. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/src/fastapi_authly/core/config/base.py +10 -1
  5. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/src/fastapi_authly/core/logging.py +111 -5
  6. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/.gitignore +0 -0
  7. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/LICENSE +0 -0
  8. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/README.md +0 -0
  9. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/README.zh.md +0 -0
  10. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/ScienceClaw-master.zip +0 -0
  11. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/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
  12. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/examples/chart_tools.py +0 -0
  13. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/examples/correct_usage.py +0 -0
  14. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/src/fastapi_authly/__init__.py +0 -0
  15. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/src/fastapi_authly/ai/__init__.py +0 -0
  16. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/src/fastapi_authly/ai/embeddings.py +0 -0
  17. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/src/fastapi_authly/ai/output_parsers.py +0 -0
  18. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/src/fastapi_authly/ai/rerank.py +0 -0
  19. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/src/fastapi_authly/ai/vectorstores.py +0 -0
  20. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/src/fastapi_authly/auth.py +0 -0
  21. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/src/fastapi_authly/charts.py +0 -0
  22. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/src/fastapi_authly/contrib/__init__.py +0 -0
  23. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/src/fastapi_authly/contrib/tortoise_pg.py +0 -0
  24. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/src/fastapi_authly/core/__init__.py +0 -0
  25. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/src/fastapi_authly/core/config/__init__.py +0 -0
  26. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/src/fastapi_authly/core/config/auth.py +0 -0
  27. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/src/fastapi_authly/core/config/database.py +0 -0
  28. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/src/fastapi_authly/core/error_codes.py +0 -0
  29. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/src/fastapi_authly/core/exception_handlers.py +0 -0
  30. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/src/fastapi_authly/core/exceptions.py +0 -0
  31. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/src/fastapi_authly/core/pagination.py +0 -0
  32. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/src/fastapi_authly/core/response.py +0 -0
  33. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/src/fastapi_authly/core/security.py +0 -0
  34. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/src/fastapi_authly/deps.py +0 -0
  35. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/src/fastapi_authly/docs.py +0 -0
  36. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/src/fastapi_authly/interfaces.py +0 -0
  37. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/src/fastapi_authly/llm/__init__.py +0 -0
  38. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/src/fastapi_authly/llm/config.py +0 -0
  39. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/src/fastapi_authly/llm/exceptions.py +0 -0
  40. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/src/fastapi_authly/llm/factory.py +0 -0
  41. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/src/fastapi_authly/llm/providers.py +0 -0
  42. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/src/fastapi_authly/models/__init__.py +0 -0
  43. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/src/fastapi_authly/models/user.py +0 -0
  44. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/src/fastapi_authly/repo.py +0 -0
  45. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/src/fastapi_authly/schemas/__init__.py +0 -0
  46. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/src/fastapi_authly/schemas/user.py +0 -0
  47. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/src/fastapi_authly/skill_runtime/__init__.py +0 -0
  48. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/src/fastapi_authly/skill_runtime/langchain_adapter.py +0 -0
  49. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/src/fastapi_authly/skill_runtime/models.py +0 -0
  50. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/src/fastapi_authly/skill_runtime/registry.py +0 -0
  51. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/src/fastapi_authly/skill_runtime/runtime.py +0 -0
  52. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/src/fastapi_authly/static/echarts.min.js +0 -0
  53. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/src/fastapi_authly/static/favicon.svg +0 -0
  54. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/src/fastapi_authly/static/scalar/standalone.js +0 -0
  55. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/src/fastapi_authly/static/scalar/style.css +0 -0
  56. {fastapi_authly-0.1.26 → fastapi_authly-0.1.28}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: fastapi-authly
3
- Version: 0.1.26
3
+ Version: 0.1.28
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/
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "fastapi-authly"
7
- version = "0.1.26"
7
+ version = "0.1.28"
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"}
@@ -1,3 +1,3 @@
1
1
  """Version information."""
2
2
 
3
- __version__ = "0.1.26"
3
+ __version__ = "0.1.28"
@@ -1,5 +1,6 @@
1
1
  from pydantic import BaseModel, Field, field_validator
2
2
  from typing import Any, Optional, Literal
3
+ from pathlib import Path
3
4
 
4
5
  class BaseBackendModel(BaseModel):
5
6
  @field_validator("*", mode="before")
@@ -30,6 +31,14 @@ class MinioConfig(BaseBackendModel):
30
31
  bucket_name: str = Field(..., description="MinIO 存储桶名称")
31
32
 
32
33
 
34
+ class LoggingFileConfig(BaseModel):
35
+ enabled: bool = False
36
+ path: Path = Path("logs/app.jsonl")
37
+ max_bytes: int = Field(default=100 * 1024 * 1024, gt=0)
38
+ backup_count: int = Field(default=5, ge=0)
39
+
40
+
33
41
  class LoggingConfig(BaseModel):
34
42
  level: Literal["DEBUG","INFO","WARNING","ERROR","CRITICAL"] = "INFO"
35
- format: Literal["console","rich","json"] = "console"
43
+ format: Literal["console", "rich"] = "console"
44
+ file: LoggingFileConfig = Field(default_factory=LoggingFileConfig)
@@ -1,11 +1,15 @@
1
1
  import inspect
2
+ import json
2
3
  import logging
3
4
  import os
4
5
  import re
5
6
  import sys
6
7
  from contextvars import ContextVar
8
+ from datetime import datetime, timezone
9
+ from logging.handlers import RotatingFileHandler
10
+ from pathlib import Path
7
11
  from time import perf_counter
8
- from typing import Literal
12
+ from typing import Any, Literal
9
13
  from uuid import uuid4
10
14
 
11
15
  from fastapi import FastAPI, Request
@@ -20,8 +24,24 @@ _http_logger = logging.getLogger("http.request")
20
24
 
21
25
 
22
26
  class RequestContextFilter(logging.Filter):
27
+ def __init__(
28
+ self,
29
+ *,
30
+ service: str | None = None,
31
+ environment: str | None = None,
32
+ version: str | None = None,
33
+ ) -> None:
34
+ super().__init__()
35
+ self.service = service
36
+ self.environment = environment
37
+ self.version = version
38
+
23
39
  def filter(self, record: logging.LogRecord) -> bool:
24
40
  request_id = _REQUEST_ID.get()
41
+ record.request_id = request_id
42
+ record.service = getattr(record, "service", self.service)
43
+ record.environment = getattr(record, "environment", self.environment)
44
+ record.version = getattr(record, "version", self.version)
25
45
  record.request_context = (
26
46
  f"request_id={request_id} | " if request_id is not None else ""
27
47
  )
@@ -33,6 +53,48 @@ class RequestContextFilter(logging.Filter):
33
53
  return True
34
54
 
35
55
 
56
+ class JsonLinesFormatter(logging.Formatter):
57
+ def format(self, record: logging.LogRecord) -> str:
58
+ payload: dict[str, Any] = {
59
+ "schema_version": "1.0",
60
+ "timestamp": datetime.fromtimestamp(
61
+ record.created,
62
+ tz=timezone.utc,
63
+ ).isoformat(),
64
+ "level": record.levelname,
65
+ "logger": record.name,
66
+ "message": record.getMessage(),
67
+ "module": record.module,
68
+ "function": record.funcName,
69
+ "line": record.lineno,
70
+ }
71
+
72
+ for field in (
73
+ "service",
74
+ "environment",
75
+ "version",
76
+ "request_id",
77
+ "endpoint",
78
+ "method",
79
+ "path",
80
+ "status_code",
81
+ "duration_ms",
82
+ ):
83
+ value = getattr(record, field, None)
84
+ if value is not None:
85
+ payload[field] = value
86
+
87
+ if record.exc_info:
88
+ exception_type, exception, _ = record.exc_info
89
+ payload["exception"] = {
90
+ "type": exception_type.__name__,
91
+ "message": str(exception),
92
+ "stacktrace": self.formatException(record.exc_info),
93
+ }
94
+
95
+ return json.dumps(payload, ensure_ascii=False, default=str)
96
+
97
+
36
98
  def _normalize_level(level: int | str | None) -> int:
37
99
  value = level or os.getenv("LOG_LEVEL", "INFO")
38
100
  if isinstance(value, int):
@@ -76,10 +138,25 @@ def _get_endpoint_name(request: Request) -> str:
76
138
  def setup_logging(
77
139
  level: int | str | None = None,
78
140
  format: LogFormat | str | None = None,
141
+ *,
142
+ service: str | None = None,
143
+ environment: str | None = None,
144
+ version: str | None = None,
145
+ file_path: str | Path | None = None,
146
+ file_max_bytes: int = 100 * 1024 * 1024,
147
+ file_backup_count: int = 5,
79
148
  ) -> None:
80
149
  resolved_level = _normalize_level(level)
81
150
  resolved_format = _normalize_format(format)
82
- context_filter = RequestContextFilter()
151
+ if file_max_bytes <= 0:
152
+ raise ValueError("file_max_bytes 必须大于 0")
153
+ if file_backup_count < 0:
154
+ raise ValueError("file_backup_count 不能小于 0")
155
+ context_filter = RequestContextFilter(
156
+ service=service,
157
+ environment=environment,
158
+ version=version,
159
+ )
83
160
 
84
161
  if resolved_format == "rich":
85
162
  handler = RichHandler(
@@ -110,10 +187,28 @@ def setup_logging(
110
187
  )
111
188
 
112
189
  handler.addFilter(context_filter)
190
+ handlers: list[logging.Handler] = [handler]
191
+
192
+ resolved_file_path = file_path or os.getenv("LOG_FILE_PATH")
193
+ if resolved_file_path:
194
+ log_path = Path(resolved_file_path)
195
+ log_path.parent.mkdir(parents=True, exist_ok=True)
196
+ file_handler = RotatingFileHandler(
197
+ log_path,
198
+ maxBytes=file_max_bytes,
199
+ backupCount=file_backup_count,
200
+ encoding="utf-8",
201
+ )
202
+ file_handler.setLevel(resolved_level)
203
+ file_handler.setFormatter(JsonLinesFormatter())
204
+ file_handler.addFilter(context_filter)
205
+ handlers.append(file_handler)
206
+
113
207
  root_logger = logging.getLogger()
114
208
  root_logger.handlers.clear()
115
209
  root_logger.setLevel(resolved_level)
116
- root_logger.addHandler(handler)
210
+ for configured_handler in handlers:
211
+ root_logger.addHandler(configured_handler)
117
212
 
118
213
  for logger_name in ("uvicorn", "uvicorn.error"):
119
214
  framework_logger = logging.getLogger(logger_name)
@@ -160,7 +255,12 @@ def setup_request_logging(app: FastAPI) -> None:
160
255
  request.method,
161
256
  path,
162
257
  duration_ms,
163
- extra={"endpoint": _get_endpoint_name(request)},
258
+ extra={
259
+ "endpoint": _get_endpoint_name(request),
260
+ "method": request.method,
261
+ "path": path,
262
+ "duration_ms": round(duration_ms, 2),
263
+ },
164
264
  )
165
265
  raise
166
266
  else:
@@ -179,7 +279,13 @@ def setup_request_logging(app: FastAPI) -> None:
179
279
  path,
180
280
  status_code,
181
281
  duration_ms,
182
- extra={"endpoint": _get_endpoint_name(request)},
282
+ extra={
283
+ "endpoint": _get_endpoint_name(request),
284
+ "method": request.method,
285
+ "path": path,
286
+ "status_code": status_code,
287
+ "duration_ms": round(duration_ms, 2),
288
+ },
183
289
  )
184
290
  response.headers["X-Request-ID"] = request_id
185
291
  return response
File without changes
File without changes