linglong-web 0.0.1__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.
- linglong_web-0.0.1/LICENSE +21 -0
- linglong_web-0.0.1/MANIFEST.in +14 -0
- linglong_web-0.0.1/PKG-INFO +249 -0
- linglong_web-0.0.1/README.md +180 -0
- linglong_web-0.0.1/linglong_web/__init__.py +132 -0
- linglong_web-0.0.1/linglong_web/__version__.py +9 -0
- linglong_web-0.0.1/linglong_web/core/__init__.py +54 -0
- linglong_web-0.0.1/linglong_web/core/auth.py +31 -0
- linglong_web-0.0.1/linglong_web/core/cacher.py +92 -0
- linglong_web-0.0.1/linglong_web/core/cluster_lock.py +128 -0
- linglong_web-0.0.1/linglong_web/core/config.py +196 -0
- linglong_web-0.0.1/linglong_web/core/constants.py +38 -0
- linglong_web-0.0.1/linglong_web/core/cors.py +52 -0
- linglong_web-0.0.1/linglong_web/core/db.py +12 -0
- linglong_web-0.0.1/linglong_web/core/ddl_manager.py +457 -0
- linglong_web-0.0.1/linglong_web/core/errors.py +118 -0
- linglong_web-0.0.1/linglong_web/core/http.py +305 -0
- linglong_web-0.0.1/linglong_web/core/limiter.py +55 -0
- linglong_web-0.0.1/linglong_web/core/limiter_local.py +84 -0
- linglong_web-0.0.1/linglong_web/core/py.typed +0 -0
- linglong_web-0.0.1/linglong_web/core/resource.py +676 -0
- linglong_web-0.0.1/linglong_web/core/response.py +86 -0
- linglong_web-0.0.1/linglong_web/core/router.py +57 -0
- linglong_web-0.0.1/linglong_web/core/scheduler.py +78 -0
- linglong_web-0.0.1/linglong_web/core/schemas.py +74 -0
- linglong_web-0.0.1/linglong_web/core/server.py +364 -0
- linglong_web-0.0.1/linglong_web/core/server_extensions.py +23 -0
- linglong_web-0.0.1/linglong_web/core/types.py +17 -0
- linglong_web-0.0.1/linglong_web/py.typed +0 -0
- linglong_web-0.0.1/linglong_web/utils/__init__.py +8 -0
- linglong_web-0.0.1/linglong_web/utils/async_read_write_lock.py +113 -0
- linglong_web-0.0.1/linglong_web/utils/context.py +58 -0
- linglong_web-0.0.1/linglong_web/utils/ddl_manager.py +1110 -0
- linglong_web-0.0.1/linglong_web/utils/log.py +84 -0
- linglong_web-0.0.1/linglong_web/utils/pj_struct.py +12 -0
- linglong_web-0.0.1/linglong_web/utils/signal_handler.py +227 -0
- linglong_web-0.0.1/linglong_web/utils/time.py +16 -0
- linglong_web-0.0.1/linglong_web.egg-info/PKG-INFO +249 -0
- linglong_web-0.0.1/linglong_web.egg-info/SOURCES.txt +69 -0
- linglong_web-0.0.1/linglong_web.egg-info/dependency_links.txt +1 -0
- linglong_web-0.0.1/linglong_web.egg-info/not-zip-safe +1 -0
- linglong_web-0.0.1/linglong_web.egg-info/requires.txt +50 -0
- linglong_web-0.0.1/linglong_web.egg-info/top_level.txt +1 -0
- linglong_web-0.0.1/pyproject.toml +174 -0
- linglong_web-0.0.1/setup.cfg +4 -0
- linglong_web-0.0.1/tests/test_auth.py +29 -0
- linglong_web-0.0.1/tests/test_cacher.py +67 -0
- linglong_web-0.0.1/tests/test_cluster_lock.py +61 -0
- linglong_web-0.0.1/tests/test_config.py +65 -0
- linglong_web-0.0.1/tests/test_core_cacher.py +67 -0
- linglong_web-0.0.1/tests/test_core_ddl_manager.py +155 -0
- linglong_web-0.0.1/tests/test_core_ddl_manager_algorithms.py +114 -0
- linglong_web-0.0.1/tests/test_core_ddl_manager_check_and_init_no_db.py +125 -0
- linglong_web-0.0.1/tests/test_cors_and_time_utils.py +97 -0
- linglong_web-0.0.1/tests/test_ddl_manager.py +343 -0
- linglong_web-0.0.1/tests/test_errors_more.py +33 -0
- linglong_web-0.0.1/tests/test_http_client.py +85 -0
- linglong_web-0.0.1/tests/test_http_client_behavior.py +111 -0
- linglong_web-0.0.1/tests/test_limiter.py +86 -0
- linglong_web-0.0.1/tests/test_limiter_local.py +61 -0
- linglong_web-0.0.1/tests/test_resource.py +29 -0
- linglong_web-0.0.1/tests/test_resource_ensure_database_exists.py +105 -0
- linglong_web-0.0.1/tests/test_resource_init_and_close_more.py +193 -0
- linglong_web-0.0.1/tests/test_resource_init_from_conf_mapping.py +151 -0
- linglong_web-0.0.1/tests/test_resource_manager_internals.py +200 -0
- linglong_web-0.0.1/tests/test_response.py +47 -0
- linglong_web-0.0.1/tests/test_router.py +28 -0
- linglong_web-0.0.1/tests/test_scheduler.py +58 -0
- linglong_web-0.0.1/tests/test_server.py +402 -0
- linglong_web-0.0.1/tests/test_utils_context.py +28 -0
- linglong_web-0.0.1/tests/test_utils_signal_handler.py +259 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Victor Lai
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
global-exclude __pycache__ *.py[cod] *.pyo *.pyd .DS_Store
|
|
2
|
+
prune .pytest_cache
|
|
3
|
+
prune .mypy_cache
|
|
4
|
+
prune .ruff_cache
|
|
5
|
+
prune .venv
|
|
6
|
+
prune build
|
|
7
|
+
prune dist
|
|
8
|
+
exclude .coverage
|
|
9
|
+
exclude coverage.xml
|
|
10
|
+
include README.md
|
|
11
|
+
include LICENSE
|
|
12
|
+
include pyproject.toml
|
|
13
|
+
recursive-include linglong_web *.py
|
|
14
|
+
recursive-include linglong_web *.typed
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: linglong-web
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Asynchronous FastAPI toolkit with service bootstrap, resource orchestration, and observability utilities
|
|
5
|
+
Author-email: Victor Lai <victor.lai@foxmail.com>
|
|
6
|
+
Maintainer-email: Victor Lai <victor.lai@foxmail.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/10000ms/linglong_web
|
|
9
|
+
Project-URL: Repository, https://github.com/10000ms/linglong_web
|
|
10
|
+
Project-URL: Documentation, https://github.com/10000ms/linglong_web/tree/master/docs
|
|
11
|
+
Project-URL: Bug Tracker, https://github.com/10000ms/linglong_web/issues
|
|
12
|
+
Keywords: fastapi,asyncio,microservices,service-registry,resource-manager,scheduler,http-client
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
21
|
+
Classifier: Framework :: FastAPI
|
|
22
|
+
Classifier: Framework :: AsyncIO
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: fastapi<1.0.0,>=0.115.0
|
|
27
|
+
Requires-Dist: uvicorn[standard]<1.0.0,>=0.30.0
|
|
28
|
+
Requires-Dist: aiohttp<4.0.0,>=3.9.0
|
|
29
|
+
Requires-Dist: aioclock<1.0.0,>=0.3.0
|
|
30
|
+
Requires-Dist: limits<7.0.0,>=3.0.0
|
|
31
|
+
Requires-Dist: redis<9.0.0,>=5.0.0
|
|
32
|
+
Requires-Dist: orjson<4.0.0,>=3.9.0
|
|
33
|
+
Requires-Dist: pydantic<3.0.0,>=2.0.0
|
|
34
|
+
Requires-Dist: nanoid<4.0.0,>=2.0.0
|
|
35
|
+
Requires-Dist: yarl<2.0.0,>=1.9.0
|
|
36
|
+
Provides-Extra: postgres
|
|
37
|
+
Requires-Dist: asyncpg<1.0.0,>=0.29.0; extra == "postgres"
|
|
38
|
+
Requires-Dist: sqlalchemy[asyncio]<3.0.0,>=2.0.0; extra == "postgres"
|
|
39
|
+
Provides-Extra: mongo
|
|
40
|
+
Requires-Dist: pymongo<5.0.0,>=4.9.0; extra == "mongo"
|
|
41
|
+
Provides-Extra: rabbitmq
|
|
42
|
+
Requires-Dist: aio-pika<10.0.0,>=9.4.0; extra == "rabbitmq"
|
|
43
|
+
Provides-Extra: celery
|
|
44
|
+
Requires-Dist: celery<6.0.0,>=5.3.0; extra == "celery"
|
|
45
|
+
Provides-Extra: speedups
|
|
46
|
+
Requires-Dist: redis[hiredis]<9.0.0,>=5.0.0; extra == "speedups"
|
|
47
|
+
Provides-Extra: all
|
|
48
|
+
Requires-Dist: asyncpg<1.0.0,>=0.29.0; extra == "all"
|
|
49
|
+
Requires-Dist: sqlalchemy[asyncio]<3.0.0,>=2.0.0; extra == "all"
|
|
50
|
+
Requires-Dist: pymongo<5.0.0,>=4.9.0; extra == "all"
|
|
51
|
+
Requires-Dist: aio-pika<10.0.0,>=9.4.0; extra == "all"
|
|
52
|
+
Requires-Dist: celery<6.0.0,>=5.3.0; extra == "all"
|
|
53
|
+
Requires-Dist: redis[hiredis]<9.0.0,>=5.0.0; extra == "all"
|
|
54
|
+
Provides-Extra: dev
|
|
55
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
56
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
|
|
57
|
+
Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
|
|
58
|
+
Requires-Dist: pytest-mock>=3.12.0; extra == "dev"
|
|
59
|
+
Requires-Dist: aiosqlite>=0.19.0; extra == "dev"
|
|
60
|
+
Requires-Dist: black>=24.0.0; extra == "dev"
|
|
61
|
+
Requires-Dist: isort>=5.13.0; extra == "dev"
|
|
62
|
+
Requires-Dist: mypy>=1.8.0; extra == "dev"
|
|
63
|
+
Requires-Dist: pre-commit>=3.6.0; extra == "dev"
|
|
64
|
+
Provides-Extra: docs
|
|
65
|
+
Requires-Dist: mkdocs>=1.5.0; extra == "docs"
|
|
66
|
+
Requires-Dist: mkdocs-material>=9.5.0; extra == "docs"
|
|
67
|
+
Requires-Dist: mkdocstrings[python]>=0.24.0; extra == "docs"
|
|
68
|
+
Dynamic: license-file
|
|
69
|
+
|
|
70
|
+
# Linglong Web
|
|
71
|
+
|
|
72
|
+
[](https://www.python.org/downloads/)
|
|
73
|
+
[](LICENSE)
|
|
74
|
+
[](https://github.com/psf/black)
|
|
75
|
+
|
|
76
|
+
Linglong Web is a FastAPI toolkit for microservice scenarios, focusing on "simple startup, flexible extension, and complete observability".
|
|
77
|
+
|
|
78
|
+
中文文档:see [README.zh-CN.md](README.zh-CN.md)
|
|
79
|
+
|
|
80
|
+
## Links
|
|
81
|
+
|
|
82
|
+
- GitHub: https://github.com/10000ms/linglong_web
|
|
83
|
+
- Issues: https://github.com/10000ms/linglong_web/issues
|
|
84
|
+
- Changelog: https://github.com/10000ms/linglong_web/blob/master/CHANGELOG.md
|
|
85
|
+
|
|
86
|
+
## Why this library
|
|
87
|
+
|
|
88
|
+
When building microservices with FastAPI, you often need:
|
|
89
|
+
|
|
90
|
+
- **Service lifecycle management** - startup, shutdown, health checks, graceful termination
|
|
91
|
+
- **Resource orchestration** - database connections, cache, message queues, schedulers
|
|
92
|
+
- **Observability** - request-id propagation, structured logging, rate limiting
|
|
93
|
+
- **Extensibility** - plugin hooks for service registration, remote config, etc.
|
|
94
|
+
|
|
95
|
+
Linglong Web provides all these out of the box as modular components.
|
|
96
|
+
|
|
97
|
+
## Features
|
|
98
|
+
|
|
99
|
+
- **AppServer lifecycle** - Built-in middleware, error handling, health checks, and signal handling
|
|
100
|
+
- **Extension hooks** - `on_config_ready`, `on_app_created`, `on_startup`, `on_shutdown` for custom logic injection
|
|
101
|
+
- **Thread-safe config** - `LinglongConfig` with hot reload support
|
|
102
|
+
- **Resource management** - Unified initialization for PostgreSQL, Redis, MongoDB, RabbitMQ, Celery
|
|
103
|
+
- **HTTP client** - aiohttp wrapper with request-id propagation and unified error handling
|
|
104
|
+
- **Decorators** - Redis cache, rate limit, and distributed lock decorators
|
|
105
|
+
- **Scheduler** - aioclock integration for periodic tasks
|
|
106
|
+
- **Bilingual comments** - Chinese and English comments throughout the codebase
|
|
107
|
+
|
|
108
|
+
## Installation
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
# Core: FastAPI bootstrapper + HTTP client + scheduler + Redis-backed decorators
|
|
112
|
+
pip install linglong-web
|
|
113
|
+
|
|
114
|
+
# Add resource backends on demand
|
|
115
|
+
pip install "linglong-web[postgres]" # PostgreSQL (asyncpg + SQLAlchemy)
|
|
116
|
+
pip install "linglong-web[mongo]" # MongoDB (PyMongo async)
|
|
117
|
+
pip install "linglong-web[rabbitmq]" # RabbitMQ (aio-pika)
|
|
118
|
+
pip install "linglong-web[celery]" # Celery
|
|
119
|
+
pip install "linglong-web[all]" # everything
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`import linglong_web` always works with just the core install; a backend you
|
|
123
|
+
haven't installed only raises a clear error if you actually initialize it.
|
|
124
|
+
|
|
125
|
+
## Quick Start
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
import asyncio
|
|
129
|
+
|
|
130
|
+
from linglong_web import LinglongConfigBase, init_config
|
|
131
|
+
from linglong_web import build_success_response
|
|
132
|
+
from linglong_web import BaseRoute, ServerRouter
|
|
133
|
+
from linglong_web import LinglongAppServer
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class DevConfig(LinglongConfigBase):
|
|
137
|
+
"""Development configuration"""
|
|
138
|
+
DEBUG = True
|
|
139
|
+
SERVICE_NAME = "demo-service"
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
router = ServerRouter()
|
|
143
|
+
router.initialize([
|
|
144
|
+
BaseRoute(
|
|
145
|
+
path="/ping",
|
|
146
|
+
method="GET",
|
|
147
|
+
handler=lambda: build_success_response({"pong": True}),
|
|
148
|
+
)
|
|
149
|
+
])
|
|
150
|
+
|
|
151
|
+
init_config({"dev": DevConfig}, mode_name="dev")
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
async def main():
|
|
155
|
+
app_server = LinglongAppServer()
|
|
156
|
+
await app_server.initialize(
|
|
157
|
+
service_name="demo-service",
|
|
158
|
+
router=router.get_router(),
|
|
159
|
+
config_dict={"dev": DevConfig},
|
|
160
|
+
)
|
|
161
|
+
await app_server.start(host="0.0.0.0", port=8080)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
if __name__ == "__main__":
|
|
165
|
+
asyncio.run(main())
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Module Overview
|
|
169
|
+
|
|
170
|
+
| Module | Description |
|
|
171
|
+
| --- | --- |
|
|
172
|
+
| `server` | FastAPI bootstrapper with middlewares & extension hooks |
|
|
173
|
+
| `server_extensions` | Lifecycle extension protocol for custom capabilities |
|
|
174
|
+
| `config` | Thread-safe config proxy with hot reload |
|
|
175
|
+
| `resource` | Unified resource bootstrapper (DB, Cache, MQ, Scheduler) |
|
|
176
|
+
| `http` | aiohttp client with request-id injection |
|
|
177
|
+
| `cacher` | Redis cache decorator |
|
|
178
|
+
| `limiter` | Rate limit decorator |
|
|
179
|
+
| `cluster_lock` | Distributed lock decorator |
|
|
180
|
+
| `scheduler` | aioclock helpers for periodic tasks |
|
|
181
|
+
| `utils` | Context, logging, signal handling, time utilities |
|
|
182
|
+
|
|
183
|
+
## Extension Example
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
from linglong_web.server_extensions import BaseServerExtension
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
class RegistryExtension(BaseServerExtension):
|
|
190
|
+
"""Example: custom service registration logic"""
|
|
191
|
+
|
|
192
|
+
def __init__(self, client):
|
|
193
|
+
self._client = client
|
|
194
|
+
|
|
195
|
+
async def on_startup(self, server):
|
|
196
|
+
payload = {"service": server.service_name, "instance": server.instance_id}
|
|
197
|
+
await self._client.register(payload)
|
|
198
|
+
server.register_shutdown_callback(lambda: self._client.deregister(payload))
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
extensions = [RegistryExtension(client)]
|
|
202
|
+
await app_server.initialize(
|
|
203
|
+
service_name="demo",
|
|
204
|
+
router=router.get_router(),
|
|
205
|
+
config_dict={"dev": DevConfig},
|
|
206
|
+
extensions=extensions,
|
|
207
|
+
)
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
> Linglong no longer bundles service registration or remote config capabilities. Implement them in your business repository using extension hooks.
|
|
211
|
+
|
|
212
|
+
## Testing & Coverage
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
# Run tests with coverage
|
|
216
|
+
pytest tests/ -v --cov=linglong_web --cov-report=term-missing
|
|
217
|
+
|
|
218
|
+
# Or with PYTHONPATH
|
|
219
|
+
PYTHONPATH=. pytest tests/ -v --cov=linglong_web --cov-report=term-missing
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
- All new modules should maintain **80%+** test coverage.
|
|
223
|
+
- Use `pytest-asyncio` and `pytest-mock` for fine-grained mocking of resources, rate limiters, and signal handlers.
|
|
224
|
+
|
|
225
|
+
## Project Layout
|
|
226
|
+
|
|
227
|
+
```
|
|
228
|
+
linglong_web/
|
|
229
|
+
├── linglong_web/
|
|
230
|
+
│ ├── core/ # Core modules
|
|
231
|
+
│ │ ├── server.py # FastAPI bootstrapper
|
|
232
|
+
│ │ ├── config.py # Config proxy
|
|
233
|
+
│ │ ├── resource.py # Resource manager
|
|
234
|
+
│ │ ├── http.py # HTTP client
|
|
235
|
+
│ │ ├── cacher.py # Cache decorator
|
|
236
|
+
│ │ ├── limiter.py # Rate limit decorator
|
|
237
|
+
│ │ └── ...
|
|
238
|
+
│ └── utils/ # Utilities
|
|
239
|
+
├── tests/ # Test suite
|
|
240
|
+
└── docs/ # Documentation
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## Versioning
|
|
244
|
+
|
|
245
|
+
The package version is sourced from `linglong_web/__version__.py`.
|
|
246
|
+
|
|
247
|
+
## License
|
|
248
|
+
|
|
249
|
+
MIT License. See LICENSE.
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# Linglong Web
|
|
2
|
+
|
|
3
|
+
[](https://www.python.org/downloads/)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](https://github.com/psf/black)
|
|
6
|
+
|
|
7
|
+
Linglong Web is a FastAPI toolkit for microservice scenarios, focusing on "simple startup, flexible extension, and complete observability".
|
|
8
|
+
|
|
9
|
+
中文文档:see [README.zh-CN.md](README.zh-CN.md)
|
|
10
|
+
|
|
11
|
+
## Links
|
|
12
|
+
|
|
13
|
+
- GitHub: https://github.com/10000ms/linglong_web
|
|
14
|
+
- Issues: https://github.com/10000ms/linglong_web/issues
|
|
15
|
+
- Changelog: https://github.com/10000ms/linglong_web/blob/master/CHANGELOG.md
|
|
16
|
+
|
|
17
|
+
## Why this library
|
|
18
|
+
|
|
19
|
+
When building microservices with FastAPI, you often need:
|
|
20
|
+
|
|
21
|
+
- **Service lifecycle management** - startup, shutdown, health checks, graceful termination
|
|
22
|
+
- **Resource orchestration** - database connections, cache, message queues, schedulers
|
|
23
|
+
- **Observability** - request-id propagation, structured logging, rate limiting
|
|
24
|
+
- **Extensibility** - plugin hooks for service registration, remote config, etc.
|
|
25
|
+
|
|
26
|
+
Linglong Web provides all these out of the box as modular components.
|
|
27
|
+
|
|
28
|
+
## Features
|
|
29
|
+
|
|
30
|
+
- **AppServer lifecycle** - Built-in middleware, error handling, health checks, and signal handling
|
|
31
|
+
- **Extension hooks** - `on_config_ready`, `on_app_created`, `on_startup`, `on_shutdown` for custom logic injection
|
|
32
|
+
- **Thread-safe config** - `LinglongConfig` with hot reload support
|
|
33
|
+
- **Resource management** - Unified initialization for PostgreSQL, Redis, MongoDB, RabbitMQ, Celery
|
|
34
|
+
- **HTTP client** - aiohttp wrapper with request-id propagation and unified error handling
|
|
35
|
+
- **Decorators** - Redis cache, rate limit, and distributed lock decorators
|
|
36
|
+
- **Scheduler** - aioclock integration for periodic tasks
|
|
37
|
+
- **Bilingual comments** - Chinese and English comments throughout the codebase
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Core: FastAPI bootstrapper + HTTP client + scheduler + Redis-backed decorators
|
|
43
|
+
pip install linglong-web
|
|
44
|
+
|
|
45
|
+
# Add resource backends on demand
|
|
46
|
+
pip install "linglong-web[postgres]" # PostgreSQL (asyncpg + SQLAlchemy)
|
|
47
|
+
pip install "linglong-web[mongo]" # MongoDB (PyMongo async)
|
|
48
|
+
pip install "linglong-web[rabbitmq]" # RabbitMQ (aio-pika)
|
|
49
|
+
pip install "linglong-web[celery]" # Celery
|
|
50
|
+
pip install "linglong-web[all]" # everything
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`import linglong_web` always works with just the core install; a backend you
|
|
54
|
+
haven't installed only raises a clear error if you actually initialize it.
|
|
55
|
+
|
|
56
|
+
## Quick Start
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
import asyncio
|
|
60
|
+
|
|
61
|
+
from linglong_web import LinglongConfigBase, init_config
|
|
62
|
+
from linglong_web import build_success_response
|
|
63
|
+
from linglong_web import BaseRoute, ServerRouter
|
|
64
|
+
from linglong_web import LinglongAppServer
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class DevConfig(LinglongConfigBase):
|
|
68
|
+
"""Development configuration"""
|
|
69
|
+
DEBUG = True
|
|
70
|
+
SERVICE_NAME = "demo-service"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
router = ServerRouter()
|
|
74
|
+
router.initialize([
|
|
75
|
+
BaseRoute(
|
|
76
|
+
path="/ping",
|
|
77
|
+
method="GET",
|
|
78
|
+
handler=lambda: build_success_response({"pong": True}),
|
|
79
|
+
)
|
|
80
|
+
])
|
|
81
|
+
|
|
82
|
+
init_config({"dev": DevConfig}, mode_name="dev")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
async def main():
|
|
86
|
+
app_server = LinglongAppServer()
|
|
87
|
+
await app_server.initialize(
|
|
88
|
+
service_name="demo-service",
|
|
89
|
+
router=router.get_router(),
|
|
90
|
+
config_dict={"dev": DevConfig},
|
|
91
|
+
)
|
|
92
|
+
await app_server.start(host="0.0.0.0", port=8080)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
if __name__ == "__main__":
|
|
96
|
+
asyncio.run(main())
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Module Overview
|
|
100
|
+
|
|
101
|
+
| Module | Description |
|
|
102
|
+
| --- | --- |
|
|
103
|
+
| `server` | FastAPI bootstrapper with middlewares & extension hooks |
|
|
104
|
+
| `server_extensions` | Lifecycle extension protocol for custom capabilities |
|
|
105
|
+
| `config` | Thread-safe config proxy with hot reload |
|
|
106
|
+
| `resource` | Unified resource bootstrapper (DB, Cache, MQ, Scheduler) |
|
|
107
|
+
| `http` | aiohttp client with request-id injection |
|
|
108
|
+
| `cacher` | Redis cache decorator |
|
|
109
|
+
| `limiter` | Rate limit decorator |
|
|
110
|
+
| `cluster_lock` | Distributed lock decorator |
|
|
111
|
+
| `scheduler` | aioclock helpers for periodic tasks |
|
|
112
|
+
| `utils` | Context, logging, signal handling, time utilities |
|
|
113
|
+
|
|
114
|
+
## Extension Example
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from linglong_web.server_extensions import BaseServerExtension
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class RegistryExtension(BaseServerExtension):
|
|
121
|
+
"""Example: custom service registration logic"""
|
|
122
|
+
|
|
123
|
+
def __init__(self, client):
|
|
124
|
+
self._client = client
|
|
125
|
+
|
|
126
|
+
async def on_startup(self, server):
|
|
127
|
+
payload = {"service": server.service_name, "instance": server.instance_id}
|
|
128
|
+
await self._client.register(payload)
|
|
129
|
+
server.register_shutdown_callback(lambda: self._client.deregister(payload))
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
extensions = [RegistryExtension(client)]
|
|
133
|
+
await app_server.initialize(
|
|
134
|
+
service_name="demo",
|
|
135
|
+
router=router.get_router(),
|
|
136
|
+
config_dict={"dev": DevConfig},
|
|
137
|
+
extensions=extensions,
|
|
138
|
+
)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
> Linglong no longer bundles service registration or remote config capabilities. Implement them in your business repository using extension hooks.
|
|
142
|
+
|
|
143
|
+
## Testing & Coverage
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
# Run tests with coverage
|
|
147
|
+
pytest tests/ -v --cov=linglong_web --cov-report=term-missing
|
|
148
|
+
|
|
149
|
+
# Or with PYTHONPATH
|
|
150
|
+
PYTHONPATH=. pytest tests/ -v --cov=linglong_web --cov-report=term-missing
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
- All new modules should maintain **80%+** test coverage.
|
|
154
|
+
- Use `pytest-asyncio` and `pytest-mock` for fine-grained mocking of resources, rate limiters, and signal handlers.
|
|
155
|
+
|
|
156
|
+
## Project Layout
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
linglong_web/
|
|
160
|
+
├── linglong_web/
|
|
161
|
+
│ ├── core/ # Core modules
|
|
162
|
+
│ │ ├── server.py # FastAPI bootstrapper
|
|
163
|
+
│ │ ├── config.py # Config proxy
|
|
164
|
+
│ │ ├── resource.py # Resource manager
|
|
165
|
+
│ │ ├── http.py # HTTP client
|
|
166
|
+
│ │ ├── cacher.py # Cache decorator
|
|
167
|
+
│ │ ├── limiter.py # Rate limit decorator
|
|
168
|
+
│ │ └── ...
|
|
169
|
+
│ └── utils/ # Utilities
|
|
170
|
+
├── tests/ # Test suite
|
|
171
|
+
└── docs/ # Documentation
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Versioning
|
|
175
|
+
|
|
176
|
+
The package version is sourced from `linglong_web/__version__.py`.
|
|
177
|
+
|
|
178
|
+
## License
|
|
179
|
+
|
|
180
|
+
MIT License. See LICENSE.
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""Linglong Web – 异步 FastAPI 工具集 / Asynchronous FastAPI toolkit."""
|
|
2
|
+
from .__version__ import (
|
|
3
|
+
__author__,
|
|
4
|
+
__description__,
|
|
5
|
+
__license__,
|
|
6
|
+
__version__,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
from .core.auth import login_required
|
|
10
|
+
from .core.cacher import cacher
|
|
11
|
+
from .core.cluster_lock import cluster_lock
|
|
12
|
+
from .core.config import (
|
|
13
|
+
LinglongConfigBase,
|
|
14
|
+
LinglongConfig,
|
|
15
|
+
init_config,
|
|
16
|
+
)
|
|
17
|
+
from .core.constants import LinglongConst
|
|
18
|
+
from .core.cors import allow_cors_specific
|
|
19
|
+
from .core.db import TableBase
|
|
20
|
+
from .core.ddl_manager import (
|
|
21
|
+
AutoDDLManager,
|
|
22
|
+
DDLManagerConfig,
|
|
23
|
+
)
|
|
24
|
+
from .core.http import (
|
|
25
|
+
HTTPClientConfig,
|
|
26
|
+
AsyncHTTPClient,
|
|
27
|
+
LinglongHTTPError,
|
|
28
|
+
http_client,
|
|
29
|
+
)
|
|
30
|
+
from .core.limiter import LimiterGuard, limiter
|
|
31
|
+
from .core.limiter_local import (
|
|
32
|
+
limiter_local,
|
|
33
|
+
reset_limiter,
|
|
34
|
+
get_limiter_stats,
|
|
35
|
+
)
|
|
36
|
+
from .core.resource import (
|
|
37
|
+
ResourceManager,
|
|
38
|
+
Rmanager,
|
|
39
|
+
DEFAULT_DB_ALIAS,
|
|
40
|
+
init_resources,
|
|
41
|
+
close_resources,
|
|
42
|
+
)
|
|
43
|
+
from .core.response import (
|
|
44
|
+
APIError,
|
|
45
|
+
APIResponse,
|
|
46
|
+
build_api_response,
|
|
47
|
+
build_success_response,
|
|
48
|
+
build_error_response,
|
|
49
|
+
)
|
|
50
|
+
from .core.router import (
|
|
51
|
+
BaseRoute,
|
|
52
|
+
ServerRouter,
|
|
53
|
+
)
|
|
54
|
+
from .core.scheduler import (
|
|
55
|
+
BaseScheduler,
|
|
56
|
+
SchedulerGroup,
|
|
57
|
+
to_group,
|
|
58
|
+
)
|
|
59
|
+
from .core.server import LinglongAppServer
|
|
60
|
+
from .core.server_extensions import BaseServerExtension
|
|
61
|
+
from .core.schemas import (
|
|
62
|
+
PgsqlConfig,
|
|
63
|
+
RedisConfig,
|
|
64
|
+
RabbitMQConfig,
|
|
65
|
+
MongoConfig,
|
|
66
|
+
CeleryConfig,
|
|
67
|
+
ResourceInitConfig,
|
|
68
|
+
)
|
|
69
|
+
from .core.errors import (
|
|
70
|
+
ErrorCode,
|
|
71
|
+
ErrorMsg,
|
|
72
|
+
LinglongHTTPException,
|
|
73
|
+
LoginRequiredError,
|
|
74
|
+
LimiterError,
|
|
75
|
+
ClusterLockError,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
__all__ = [
|
|
79
|
+
"login_required",
|
|
80
|
+
"cacher",
|
|
81
|
+
"cluster_lock",
|
|
82
|
+
"LinglongConfigBase",
|
|
83
|
+
"LinglongConfig",
|
|
84
|
+
"LinglongConst",
|
|
85
|
+
"init_config",
|
|
86
|
+
"allow_cors_specific",
|
|
87
|
+
"TableBase",
|
|
88
|
+
"AutoDDLManager",
|
|
89
|
+
"DDLManagerConfig",
|
|
90
|
+
"BaseRoute",
|
|
91
|
+
"ServerRouter",
|
|
92
|
+
"APIError",
|
|
93
|
+
"APIResponse",
|
|
94
|
+
"build_api_response",
|
|
95
|
+
"build_success_response",
|
|
96
|
+
"build_error_response",
|
|
97
|
+
"HTTPClientConfig",
|
|
98
|
+
"AsyncHTTPClient",
|
|
99
|
+
"LinglongHTTPError",
|
|
100
|
+
"http_client",
|
|
101
|
+
"LimiterGuard",
|
|
102
|
+
"limiter",
|
|
103
|
+
"limiter_local",
|
|
104
|
+
"reset_limiter",
|
|
105
|
+
"get_limiter_stats",
|
|
106
|
+
"ResourceManager",
|
|
107
|
+
"Rmanager",
|
|
108
|
+
"DEFAULT_DB_ALIAS",
|
|
109
|
+
"init_resources",
|
|
110
|
+
"close_resources",
|
|
111
|
+
"BaseScheduler",
|
|
112
|
+
"SchedulerGroup",
|
|
113
|
+
"to_group",
|
|
114
|
+
"LinglongAppServer",
|
|
115
|
+
"BaseServerExtension",
|
|
116
|
+
"PgsqlConfig",
|
|
117
|
+
"RedisConfig",
|
|
118
|
+
"RabbitMQConfig",
|
|
119
|
+
"MongoConfig",
|
|
120
|
+
"CeleryConfig",
|
|
121
|
+
"ResourceInitConfig",
|
|
122
|
+
"ErrorCode",
|
|
123
|
+
"ErrorMsg",
|
|
124
|
+
"LinglongHTTPException",
|
|
125
|
+
"LoginRequiredError",
|
|
126
|
+
"LimiterError",
|
|
127
|
+
"ClusterLockError",
|
|
128
|
+
"__version__",
|
|
129
|
+
"__author__",
|
|
130
|
+
"__license__",
|
|
131
|
+
"__description__",
|
|
132
|
+
]
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Linglong Web metadata / 版本信息模块."""
|
|
2
|
+
|
|
3
|
+
__version__ = "0.0.1"
|
|
4
|
+
__author__ = "Victor Lai"
|
|
5
|
+
__license__ = "MIT"
|
|
6
|
+
__description__ = (
|
|
7
|
+
"Asynchronous FastAPI toolkit providing service bootstrap, registry hooks, resource orchestration, "
|
|
8
|
+
"and observability helpers"
|
|
9
|
+
)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Linglong Web – 异步 FastAPI 工具集 / Asynchronous FastAPI toolkit."""
|
|
2
|
+
from .auth import login_required
|
|
3
|
+
from .cacher import cacher
|
|
4
|
+
from .cluster_lock import cluster_lock
|
|
5
|
+
from .config import (
|
|
6
|
+
LinglongConfigBase,
|
|
7
|
+
LinglongConfig,
|
|
8
|
+
LinglongConfigProxy,
|
|
9
|
+
init_config,
|
|
10
|
+
)
|
|
11
|
+
from .constants import LinglongConst
|
|
12
|
+
from .cors import allow_cors_specific
|
|
13
|
+
from .db import TableBase
|
|
14
|
+
from .ddl_manager import (
|
|
15
|
+
AutoDDLManager,
|
|
16
|
+
DDLManagerConfig,
|
|
17
|
+
)
|
|
18
|
+
from .http import (
|
|
19
|
+
HTTPClientConfig,
|
|
20
|
+
AsyncHTTPClient,
|
|
21
|
+
LinglongHTTPError,
|
|
22
|
+
http_client,
|
|
23
|
+
)
|
|
24
|
+
from .limiter import (
|
|
25
|
+
LimiterGuard,
|
|
26
|
+
limiter,
|
|
27
|
+
)
|
|
28
|
+
from .limiter_local import (
|
|
29
|
+
limiter_local,
|
|
30
|
+
reset_limiter,
|
|
31
|
+
get_limiter_stats,
|
|
32
|
+
)
|
|
33
|
+
from .resource import (
|
|
34
|
+
ResourceManager,
|
|
35
|
+
Rmanager,
|
|
36
|
+
DEFAULT_DB_ALIAS,
|
|
37
|
+
init_resources,
|
|
38
|
+
close_resources,
|
|
39
|
+
)
|
|
40
|
+
from .response import (
|
|
41
|
+
APIError,
|
|
42
|
+
APIResponse,
|
|
43
|
+
build_api_response,
|
|
44
|
+
build_success_response,
|
|
45
|
+
build_error_response,
|
|
46
|
+
)
|
|
47
|
+
from .router import BaseRoute, ServerRouter
|
|
48
|
+
from .scheduler import (
|
|
49
|
+
BaseScheduler,
|
|
50
|
+
SchedulerGroup,
|
|
51
|
+
to_group,
|
|
52
|
+
)
|
|
53
|
+
from .server import LinglongAppServer
|
|
54
|
+
from .server_extensions import BaseServerExtension
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Authentication helpers shared across Linglong services.
|
|
2
|
+
|
|
3
|
+
提供 Linglong 服务统一的认证装饰器工具。
|
|
4
|
+
"""
|
|
5
|
+
from functools import wraps
|
|
6
|
+
from typing import (
|
|
7
|
+
Awaitable,
|
|
8
|
+
Callable,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
from .errors import LoginRequiredError
|
|
12
|
+
from .types import P, R
|
|
13
|
+
from ..utils.context import get_context_user_id
|
|
14
|
+
from ..utils.log import logger
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def login_required(func: Callable[P, Awaitable[R]]) -> Callable[P, Awaitable[R]]:
|
|
18
|
+
"""Ensure the current request has a valid login user id.
|
|
19
|
+
|
|
20
|
+
确保当前请求已经完成登录校验,如果缺少用户信息则抛出 ``LoginRequiredError``。
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
@wraps(func)
|
|
24
|
+
async def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
25
|
+
user_id = get_context_user_id()
|
|
26
|
+
if user_id is not None and isinstance(user_id, int):
|
|
27
|
+
return await func(*args, **kwargs)
|
|
28
|
+
logger.warning("user is not login")
|
|
29
|
+
raise LoginRequiredError()
|
|
30
|
+
|
|
31
|
+
return wrapper
|