rainycode 1.5.1__tar.gz → 1.5.3__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 (40) hide show
  1. {rainycode-1.5.1 → rainycode-1.5.3}/PKG-INFO +1 -1
  2. rainycode-1.5.3/common_base/aiorequests.py +55 -0
  3. {rainycode-1.5.1 → rainycode-1.5.3}/common_models/file_model.py +2 -7
  4. {rainycode-1.5.1 → rainycode-1.5.3}/common_servers/api/auth_api.py +5 -4
  5. {rainycode-1.5.1 → rainycode-1.5.3}/common_servers/api/wechat_api.py +3 -2
  6. {rainycode-1.5.1 → rainycode-1.5.3}/common_servers/router.py +2 -1
  7. {rainycode-1.5.1 → rainycode-1.5.3}/common_servers/service/auth_svc.py +4 -3
  8. {rainycode-1.5.1 → rainycode-1.5.3}/common_servers/service/file_svc.py +3 -13
  9. {rainycode-1.5.1 → rainycode-1.5.3}/common_utils/datetime_util.py +2 -2
  10. {rainycode-1.5.1 → rainycode-1.5.3}/common_utils/jwt_util.py +3 -1
  11. {rainycode-1.5.1 → rainycode-1.5.3}/core/base_config.py +5 -1
  12. rainycode-1.5.3/pyproject.toml +60 -0
  13. {rainycode-1.5.1 → rainycode-1.5.3}/rainycode.egg-info/PKG-INFO +1 -1
  14. rainycode-1.5.1/common_base/aiorequests.py +0 -39
  15. rainycode-1.5.1/pyproject.toml +0 -36
  16. {rainycode-1.5.1 → rainycode-1.5.3}/common_base/consts.py +0 -0
  17. {rainycode-1.5.1 → rainycode-1.5.3}/common_base/exception.py +0 -0
  18. {rainycode-1.5.1 → rainycode-1.5.3}/common_base/logging.py +0 -0
  19. {rainycode-1.5.1 → rainycode-1.5.3}/common_base/response.py +0 -0
  20. {rainycode-1.5.1 → rainycode-1.5.3}/common_depends/auth_depend.py +0 -0
  21. {rainycode-1.5.1 → rainycode-1.5.3}/common_models/__init__.py +0 -0
  22. {rainycode-1.5.1 → rainycode-1.5.3}/common_models/base_model.py +0 -0
  23. {rainycode-1.5.1 → rainycode-1.5.3}/common_models/user_model.py +0 -0
  24. {rainycode-1.5.1 → rainycode-1.5.3}/common_models/wechat_model.py +0 -0
  25. {rainycode-1.5.1 → rainycode-1.5.3}/common_servers/schemas/auth_schema.py +0 -0
  26. {rainycode-1.5.1 → rainycode-1.5.3}/common_servers/service/wechat_svc.py +0 -0
  27. {rainycode-1.5.1 → rainycode-1.5.3}/common_utils/bcrypt_util.py +0 -0
  28. {rainycode-1.5.1 → rainycode-1.5.3}/common_utils/captcha_util.py +0 -0
  29. {rainycode-1.5.1 → rainycode-1.5.3}/common_utils/ip_util.py +0 -0
  30. {rainycode-1.5.1 → rainycode-1.5.3}/common_utils/snowflake_util.py +0 -0
  31. {rainycode-1.5.1 → rainycode-1.5.3}/common_utils/wechat_util.py +0 -0
  32. {rainycode-1.5.1 → rainycode-1.5.3}/core/databases/aiodb.py +0 -0
  33. {rainycode-1.5.1 → rainycode-1.5.3}/core/databases/aioredis.py +0 -0
  34. {rainycode-1.5.1 → rainycode-1.5.3}/core/middleware/http_middleware.py +0 -0
  35. {rainycode-1.5.1 → rainycode-1.5.3}/core/start.py +0 -0
  36. {rainycode-1.5.1 → rainycode-1.5.3}/rainycode.egg-info/SOURCES.txt +0 -0
  37. {rainycode-1.5.1 → rainycode-1.5.3}/rainycode.egg-info/dependency_links.txt +0 -0
  38. {rainycode-1.5.1 → rainycode-1.5.3}/rainycode.egg-info/requires.txt +0 -0
  39. {rainycode-1.5.1 → rainycode-1.5.3}/rainycode.egg-info/top_level.txt +0 -0
  40. {rainycode-1.5.1 → rainycode-1.5.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rainycode
3
- Version: 1.5.1
3
+ Version: 1.5.3
4
4
  Summary: FastAPI base modules
5
5
  Requires-Python: >=3.10
6
6
  Requires-Dist: aerich==0.9.2
@@ -0,0 +1,55 @@
1
+ import json
2
+ import ssl
3
+
4
+ import aiohttp
5
+ import certifi
6
+
7
+ # 模块级别 SSL 上下文,使用 certifi 证书,避免重复创建
8
+ _SSL_CONTEXT = ssl.create_default_context(cafile=certifi.where())
9
+
10
+ # 连接池:跨请求复用 keep-alive 长连接(而非每次新建 connector)。
11
+ # limit: 全池同时最大连接数(含活跃与 keep-alive 空闲复用)
12
+ # limit_per_host: 单个 host 端点同时最大连接数,限制对单一对端的并发/占用
13
+ # 惰性初始化:TCPConnector 构造需要运行中的事件循环,只能在首次请求(async 上下文)时创建,
14
+ # 不能在模块 import 时同步 create(否则抛 RuntimeError: no running event loop)。
15
+ _CONNECTOR: aiohttp.TCPConnector | None = None
16
+
17
+
18
+ def _get_connector() -> aiohttp.TCPConnector:
19
+ global _CONNECTOR
20
+ if _CONNECTOR is None:
21
+ _CONNECTOR = aiohttp.TCPConnector(ssl=_SSL_CONTEXT, limit=300, limit_per_host=100)
22
+ return _CONNECTOR
23
+
24
+
25
+ def parse_url(url, params=None, suffix=None):
26
+ if params:
27
+ query = "&".join([f"{k}={params[k]}" for k in sorted(params.keys())])
28
+ if "?" in url:
29
+ url += "&" + query
30
+ else:
31
+ url += "?" + query
32
+ if suffix:
33
+ url += suffix
34
+ return url
35
+
36
+
37
+ async def get(url, params=None, headers=None, suffix=None):
38
+ url = parse_url(url, params, suffix)
39
+ async with (
40
+ aiohttp.ClientSession(connector=_get_connector(), trust_env=True) as session,
41
+ session.get(url, headers=headers) as response,
42
+ ):
43
+ text = await response.text()
44
+ return json.loads(text)
45
+
46
+
47
+ async def post(url, data=None, headers=None):
48
+ if isinstance(data, dict):
49
+ data = json.dumps(data)
50
+ async with (
51
+ aiohttp.ClientSession(connector=_get_connector(), trust_env=True) as session,
52
+ session.post(url, data=data, headers=headers) as response,
53
+ ):
54
+ text = await response.text()
55
+ return json.loads(text)
@@ -1,17 +1,12 @@
1
- from core.base_config import base_config
2
1
  from tortoise import fields
3
2
 
4
3
  from common_models.base_model import CreateModel
4
+ from core.base_config import base_config
5
5
 
6
6
 
7
7
  class File(CreateModel):
8
8
  """
9
- 上传文件注册表:所有上传文件落一条元数据,磁盘文件本身放 upload_root 下。
10
-
11
- url 存最小相对路径(如 dishes/xxx.jpg,不含前缀),经 fix_data 序列化钩子
12
- (库 model2dict/get_data 会自动调用)加上可配置前缀(/uploads 或完整 CDN URL)
13
- 得到前端可直接访问的完整路径。业务表经外键引用 File.id(如 UserDish.image),
14
- 不冗余存 url 字符串。md5 用于识别相同文件(上传去重)。
9
+ 文件表
15
10
  """
16
11
  id = fields.IntField(primary_key=True, description="主键")
17
12
  name = fields.CharField(max_length=255, description="原始文件名")
@@ -1,9 +1,10 @@
1
+ from fastapi import APIRouter, Body, Query, Request, Security
2
+
1
3
  from common_base.response import SuccessReturn
2
4
  from common_depends.auth_depend import login_require
3
5
  from common_models.user_model import User
4
- from common_servers.schemas.auth_schema import *
5
- from common_servers.service.auth_svc import AuthService
6
- from fastapi import APIRouter, Body, Query, Request, Security
6
+ from common_servers.schemas.auth_schema import TokenResponse, UserInfo
7
+ from common_servers.service.auth_svc import AuthService, UserLogin, UserRegister, WechatLogin
7
8
 
8
9
  router = APIRouter(prefix="/auth")
9
10
 
@@ -70,4 +71,4 @@ async def get_wechat_oauth_url(
70
71
  - 如果用户未注册,自动重定向到 snsapi_userinfo 授权
71
72
  """
72
73
  authorize_url = await AuthService.get_wechat_oauth_url(app_pk, redirect_uri, state)
73
- return SuccessReturn(data={"authorize_url": authorize_url})
74
+ return SuccessReturn(data={"authorize_url": authorize_url})
@@ -1,13 +1,14 @@
1
1
  import json
2
2
  from urllib.parse import urlencode
3
3
 
4
+ from fastapi import APIRouter, Query, Request, Response
5
+ from fastapi.responses import RedirectResponse
6
+
4
7
  from common_base.consts import REDIS_PREFIX_OAUTH_STATE
5
8
  from common_base.exception import CustomException
6
9
  from common_servers.service.auth_svc import AuthService
7
10
  from common_servers.service.wechat_svc import WechatService
8
11
  from core.databases.aioredis import AioRedis
9
- from fastapi import APIRouter, Query, Request, Response
10
- from fastapi.responses import RedirectResponse
11
12
 
12
13
  router = APIRouter(prefix="/wechat")
13
14
 
@@ -1,6 +1,7 @@
1
+ from fastapi import APIRouter
2
+
1
3
  from common_servers.api.auth_api import router as auth_router
2
4
  from common_servers.api.wechat_api import router as wechat_router
3
- from fastapi import APIRouter
4
5
 
5
6
  base_router = APIRouter()
6
7
 
@@ -4,6 +4,10 @@ import uuid
4
4
  from datetime import datetime, timedelta
5
5
  from urllib.parse import urlencode
6
6
 
7
+ from fastapi.responses import RedirectResponse
8
+ from tortoise.expressions import Q
9
+ from tortoise.transactions import atomic
10
+
7
11
  from common_base.consts import (
8
12
  REDIS_PREFIX_BLOCKLIST,
9
13
  REDIS_PREFIX_LOGIN_LOCK_IP,
@@ -28,9 +32,6 @@ from common_utils.jwt_util import JwtUtil
28
32
  from common_utils.wechat_util import WechatUtil
29
33
  from core.base_config import base_config
30
34
  from core.databases.aioredis import AioRedis
31
- from fastapi.responses import RedirectResponse
32
- from tortoise.expressions import Q
33
- from tortoise.transactions import atomic
34
35
 
35
36
  # 登录限流配置
36
37
  LOGIN_FAIL_LIMIT = 5 # 最大失败次数
@@ -7,10 +7,6 @@ from common_models.file_model import File
7
7
  from common_models.user_model import User
8
8
  from core.base_config import base_config
9
9
 
10
- # 默认上传约束
11
- DEFAULT_MAX_BYTES = 5 * 1024 * 1024 # 5MB
12
- ALLOWED_IMAGE_EXTS = {'jpg', 'jpeg', 'png', 'webp'}
13
-
14
10
 
15
11
  class FileService:
16
12
  """通用文件上传服务:校验格式/大小、写盘、落 File 注册表、按 md5 去重。"""
@@ -22,15 +18,9 @@ class FileService:
22
18
  file,
23
19
  *,
24
20
  subdir: str,
25
- allowed_exts: set[str] = ALLOWED_IMAGE_EXTS,
26
- max_bytes: int = DEFAULT_MAX_BYTES,
21
+ allowed_exts: set[str] = base_config.upload_allowed_image_exts,
22
+ max_bytes: int = base_config.upload_max_bytes,
27
23
  ) -> dict:
28
- """
29
- 上传文件到 upload_root/<subdir>/ 并落 File 记录,返回序列化结果(url 经 fix_data 加前缀)。
30
-
31
- 相同内容(md5 命中)复用已有 File 记录与磁盘文件,不重复写盘。
32
- file 为 FastAPI UploadFile(含 .filename/.content_type/.read())。
33
- """
34
24
  ext = file.filename.rsplit('.', 1)[-1].lower() if '.' in file.filename else ''
35
25
  if ext not in allowed_exts:
36
26
  raise CustomException(msg="不支持的文件格式")
@@ -59,4 +49,4 @@ class FileService:
59
49
  md5=md5,
60
50
  creator_id=user.id,
61
51
  )
62
- return await File.model2dict(row)
52
+ return await File.model2dict(row)
@@ -7,6 +7,6 @@ from zoneinfo import ZoneInfo
7
7
  CN_TZ = ZoneInfo('Asia/Shanghai')
8
8
 
9
9
 
10
- def now_app() -> datetime:
11
- """返回当前时刻的 aware Asia/Shanghai datetime(与 DB 读回值时间基准一致)。"""
10
+ def chinese_now() -> datetime:
11
+ """返回当前时刻的 aware Asia/Shanghai datetime"""
12
12
  return datetime.now(CN_TZ)
@@ -1,7 +1,9 @@
1
1
  from datetime import datetime, timedelta
2
2
  from typing import Any
3
+ from zoneinfo import ZoneInfo
3
4
 
4
5
  import jwt
6
+
5
7
  from core.base_config import base_config
6
8
 
7
9
 
@@ -25,7 +27,7 @@ class JwtUtil:
25
27
  str: JWT Token
26
28
  """
27
29
  to_encode = data.copy()
28
- expire = datetime.now() + expires_delta
30
+ expire = datetime.now(ZoneInfo('Asia/Shanghai')) + expires_delta
29
31
 
30
32
  to_encode.update({"exp": expire})
31
33
  encoded_jwt = jwt.encode(to_encode, cls.SECRET_KEY, algorithm=cls.ALGORITHM)
@@ -14,7 +14,7 @@ class BaseConfig(BaseSettings):
14
14
  base_url: str = ""
15
15
 
16
16
  # 文件上传:本地存储根目录 + 访问 URL 前缀。
17
- # upload_url_prefix 可为相对路径(/uploads)或完整 CDN URL(https://cdn.xxx.com/uploads),
17
+ # upload_url_prefix 可为相对路径(/uploads)或完整 URL(https://cdn.xxx.com/uploads),
18
18
  upload_root: str = 'uploads'
19
19
  upload_url_prefix: str = '/uploads'
20
20
 
@@ -32,6 +32,10 @@ class BaseConfig(BaseSettings):
32
32
  # Refresh Token 有效期
33
33
  refresh_token_expire_days: int = 30
34
34
 
35
+ # 文件上传约束(common_servers/service/file_svc.py)
36
+ upload_max_bytes: int = 5 * 1024 * 1024 # 5MB
37
+ upload_allowed_image_exts: set[str] = {'jpg', 'jpeg', 'png', 'webp'}
38
+
35
39
  model_config = SettingsConfigDict(
36
40
  env_file=('envs/dev.env', 'envs/pro.env'),
37
41
  env_file_encoding="utf-8",
@@ -0,0 +1,60 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "rainycode"
7
+ version = "1.5.3"
8
+ description = "FastAPI base modules"
9
+ requires-python = ">=3.10"
10
+
11
+ dependencies = [
12
+ "aerich == 0.9.2",
13
+ "fastapi == 0.133.1",
14
+ "aiohttp == 3.13.3",
15
+ "passlib==1.7.4",
16
+ "redis == 7.2.1",
17
+ "pyjwt == 2.11.0",
18
+ "pillow == 12.1.1",
19
+ "bcrypt == 4.0.1",
20
+ "certifi == 2026.2.25",
21
+ "cryptography == 46.0.5",
22
+ "pycryptodome == 3.23.0",
23
+ "uvicorn[standard] == 0.41.0",
24
+ "tortoise-orm[asyncmy] == 0.25.4",
25
+ "pydantic-settings == 2.13.1",
26
+ ]
27
+
28
+ [project.optional-dependencies]
29
+ release = [
30
+ "build == 1.4.0",
31
+ "twine == 6.1.0",
32
+ ]
33
+
34
+ [tool.setuptools.packages.find]
35
+ where = ["."]
36
+ include = ["core*", "common*"]
37
+
38
+
39
+ [tool.ruff]
40
+ target-version = "py312" # 按 Python 3.12 语法判断,UP 规则才不会误降级
41
+ line-length = 120 # 中文注释 + FastAPI 调用链较长,120 比 88 舒服
42
+
43
+ [tool.ruff.lint]
44
+ select = [
45
+ "E", "W", # pycodestyle:基础风格(空格/空行/换行)
46
+ "F", # pyflakes:未用 import / 变量、未定义名
47
+ "I", # isort:import 自动排序分组
48
+ "B", # bugbear:常见 bug 模式(可变默认值、裸 except 等)
49
+ "UP", # pyupgrade:语法现代化(Optional[X] → X | None 等)
50
+ ]
51
+ ignore = [
52
+ "B008", # 函数调用作为默认值 —— FastAPI 的 Security/Depends/Query/Body 必须这么写
53
+ ]
54
+
55
+ [tool.ruff.lint.per-file-ignores]
56
+ "migrations/*" = ["ALL"] # aerich 自动生成的迁移文件,不检查
57
+ "__init__.py" = ["F401"] # 模块聚合导出,允许未使用 import
58
+
59
+ [tool.ruff.lint.isort]
60
+ known-first-party = ["servers", "models"] # 本项目模块,排序时与第三方库分组
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rainycode
3
- Version: 1.5.1
3
+ Version: 1.5.3
4
4
  Summary: FastAPI base modules
5
5
  Requires-Python: >=3.10
6
6
  Requires-Dist: aerich==0.9.2
@@ -1,39 +0,0 @@
1
- import json
2
- import ssl
3
-
4
- import aiohttp
5
- import certifi
6
-
7
- # 模块级别 SSL 上下文,使用 certifi 证书,避免重复创建
8
- _SSL_CONTEXT = ssl.create_default_context(cafile=certifi.where())
9
-
10
-
11
- def parse_url(url, params=None, suffix=None):
12
- if params:
13
- query = "&".join([f"{k}={params[k]}" for k in sorted(params.keys())])
14
- if "?" in url:
15
- url += "&" + query
16
- else:
17
- url += "?" + query
18
- if suffix:
19
- url += suffix
20
- return url
21
-
22
-
23
- async def get(url, params=None, headers=None, suffix=None):
24
- url = parse_url(url, params, suffix)
25
- connector = aiohttp.TCPConnector(ssl=_SSL_CONTEXT)
26
- async with aiohttp.ClientSession(connector=connector, trust_env=True) as session:
27
- async with session.get(url, headers=headers) as response:
28
- text = await response.text()
29
- return json.loads(text)
30
-
31
-
32
- async def post(url, data=None, headers=None):
33
- connector = aiohttp.TCPConnector(ssl=_SSL_CONTEXT)
34
- async with aiohttp.ClientSession(connector=connector, trust_env=True) as session:
35
- if isinstance(data, dict):
36
- data = json.dumps(data)
37
- async with session.post(url, data=data, headers=headers) as response:
38
- text = await response.text()
39
- return json.loads(text)
@@ -1,36 +0,0 @@
1
- [build-system]
2
- requires = ["setuptools>=61.0", "wheel"]
3
- build-backend = "setuptools.build_meta"
4
-
5
- [project]
6
- name = "rainycode"
7
- version = "1.5.1"
8
- description = "FastAPI base modules"
9
- requires-python = ">=3.10"
10
-
11
- dependencies = [
12
- "aerich == 0.9.2",
13
- "fastapi == 0.133.1",
14
- "aiohttp == 3.13.3",
15
- "passlib==1.7.4",
16
- "redis == 7.2.1",
17
- "pyjwt == 2.11.0",
18
- "pillow == 12.1.1",
19
- "bcrypt == 4.0.1",
20
- "certifi == 2026.2.25",
21
- "cryptography == 46.0.5",
22
- "pycryptodome == 3.23.0",
23
- "uvicorn[standard] == 0.41.0",
24
- "tortoise-orm[asyncmy] == 0.25.4",
25
- "pydantic-settings == 2.13.1",
26
- ]
27
-
28
- [project.optional-dependencies]
29
- release = [
30
- "build == 1.4.0",
31
- "twine == 6.1.0",
32
- ]
33
-
34
- [tool.setuptools.packages.find]
35
- where = ["."]
36
- include = ["core*", "common*"]
File without changes
File without changes