yuanboot 0.1.5__py3-none-any.whl

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.

Potentially problematic release.


This version of yuanboot might be problematic. Click here for more details.

Files changed (76) hide show
  1. yuanboot/__init__.py +34 -0
  2. yuanboot/__main__.py +3 -0
  3. yuanboot/_version.py +1 -0
  4. yuanboot/cli/__init__.py +260 -0
  5. yuanboot/cli/__main__.py +3 -0
  6. yuanboot/cli/entrypoint.py +9 -0
  7. yuanboot/cli/scaffolding.py +119 -0
  8. yuanboot/cli/templates/common/.dockerignore.tpl +7 -0
  9. yuanboot/cli/templates/common/.gitignore.tpl +8 -0
  10. yuanboot/cli/templates/common/Dockerfile.tpl +10 -0
  11. yuanboot/cli/templates/common/README.md.tpl +59 -0
  12. yuanboot/cli/templates/common/__PACKAGE__/__init__.py.tpl +0 -0
  13. yuanboot/cli/templates/common/config.yml.tpl +11 -0
  14. yuanboot/cli/templates/common/config_dev.yml.tpl +3 -0
  15. yuanboot/cli/templates/common/config_fat.yml.tpl +3 -0
  16. yuanboot/cli/templates/common/config_pro.yml.tpl +3 -0
  17. yuanboot/cli/templates/common/config_uat.yml.tpl +3 -0
  18. yuanboot/cli/templates/common/docker-compose.yml.tpl +9 -0
  19. yuanboot/cli/templates/common/pyproject.toml.tpl +26 -0
  20. yuanboot/cli/templates/common/spec/backend-module-development.md.tpl +118 -0
  21. yuanboot/cli/templates/common/spec/components.md.tpl +226 -0
  22. yuanboot/cli/templates/common/spec/yuanboot-python.md.tpl +212 -0
  23. yuanboot/cli/templates/console/__PACKAGE__/main.py.tpl +29 -0
  24. yuanboot/cli/templates/console/__PACKAGE__/worker.py.tpl +14 -0
  25. yuanboot/cli/templates/grpc/__PACKAGE__/main.py.tpl +25 -0
  26. yuanboot/cli/templates/grpc/config.yml.tpl +9 -0
  27. yuanboot/cli/templates/grpc/config_dev.yml.tpl +3 -0
  28. yuanboot/cli/templates/grpc/config_fat.yml.tpl +3 -0
  29. yuanboot/cli/templates/grpc/config_pro.yml.tpl +3 -0
  30. yuanboot/cli/templates/grpc/config_uat.yml.tpl +3 -0
  31. yuanboot/cli/templates/grpc/proto/greeter.proto.tpl +6 -0
  32. yuanboot/cli/templates/microservice/__PACKAGE__/startup.py.tpl +15 -0
  33. yuanboot/cli/templates/microservice/__PACKAGE__/worker.py.tpl +16 -0
  34. yuanboot/cli/templates/mvc/__PACKAGE__/controllers.py.tpl +41 -0
  35. yuanboot/cli/templates/mvc/__PACKAGE__/startup.py.tpl +19 -0
  36. yuanboot/cli/templates/mvc/__PACKAGE__/templates/index.html.tpl +9 -0
  37. yuanboot/cli/templates/simpleweb/__PACKAGE__/startup.py.tpl +15 -0
  38. yuanboot/cli/templates/web/__PACKAGE__/main.py.tpl +27 -0
  39. yuanboot/cli/templates/webapi/__PACKAGE__/endpoints.py.tpl +25 -0
  40. yuanboot/cli/templates/webapi/__PACKAGE__/services.py.tpl +9 -0
  41. yuanboot/cli/templates/webapi/__PACKAGE__/startup.py.tpl +13 -0
  42. yuanboot/components/__init__.py +1 -0
  43. yuanboot/components/apollo.py +68 -0
  44. yuanboot/components/configuration.py +38 -0
  45. yuanboot/components/discovery.py +431 -0
  46. yuanboot/components/email.py +71 -0
  47. yuanboot/components/httpclient.py +93 -0
  48. yuanboot/components/mysql.py +136 -0
  49. yuanboot/components/nacos.py +517 -0
  50. yuanboot/components/observer.py +69 -0
  51. yuanboot/components/redis.py +107 -0
  52. yuanboot/components/scheduler.py +122 -0
  53. yuanboot/components/xxljob.py +219 -0
  54. yuanboot/configuration.py +294 -0
  55. yuanboot/console/__init__.py +3 -0
  56. yuanboot/dependency_injection.py +178 -0
  57. yuanboot/environment.py +9 -0
  58. yuanboot/grpc.py +133 -0
  59. yuanboot/health.py +14 -0
  60. yuanboot/hosting.py +335 -0
  61. yuanboot/logging.py +158 -0
  62. yuanboot/py.typed +0 -0
  63. yuanboot/web/__init__.py +30 -0
  64. yuanboot/web/application.py +144 -0
  65. yuanboot/web/context.py +43 -0
  66. yuanboot/web/dependencies.py +27 -0
  67. yuanboot/web/endpoints.py +63 -0
  68. yuanboot/web/middleware.py +97 -0
  69. yuanboot/web/mvc.py +105 -0
  70. yuanboot/web/routing.py +11 -0
  71. yuanboot/web/security.py +47 -0
  72. yuanboot-0.1.5.dist-info/METADATA +257 -0
  73. yuanboot-0.1.5.dist-info/RECORD +76 -0
  74. yuanboot-0.1.5.dist-info/WHEEL +4 -0
  75. yuanboot-0.1.5.dist-info/entry_points.txt +2 -0
  76. yuanboot-0.1.5.dist-info/licenses/LICENSE +22 -0
yuanboot/__init__.py ADDED
@@ -0,0 +1,34 @@
1
+ from yuanboot._version import __version__
2
+ from yuanboot.configuration import Configuration, ConfigurationBuilder
3
+ from yuanboot.dependency_injection import (
4
+ ServiceCollection,
5
+ ServiceLifetime,
6
+ ServiceProvider,
7
+ ServiceResolutionError,
8
+ )
9
+ from yuanboot.hosting import (
10
+ ApplicationEvent,
11
+ ApplicationLife,
12
+ BackgroundService,
13
+ Host,
14
+ HostBuilder,
15
+ HostedService,
16
+ HostEnvironment,
17
+ )
18
+
19
+ __all__ = [
20
+ "__version__",
21
+ "ApplicationEvent",
22
+ "ApplicationLife",
23
+ "BackgroundService",
24
+ "Configuration",
25
+ "ConfigurationBuilder",
26
+ "Host",
27
+ "HostBuilder",
28
+ "HostEnvironment",
29
+ "HostedService",
30
+ "ServiceCollection",
31
+ "ServiceLifetime",
32
+ "ServiceProvider",
33
+ "ServiceResolutionError",
34
+ ]
yuanboot/__main__.py ADDED
@@ -0,0 +1,3 @@
1
+ from yuanboot.cli import main
2
+
3
+ raise SystemExit(main())
yuanboot/_version.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "0.1.5"
@@ -0,0 +1,260 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import importlib.util
5
+ import os
6
+ import signal
7
+ import subprocess
8
+ import sys
9
+ import tomllib
10
+ from contextlib import contextmanager
11
+ from pathlib import Path
12
+
13
+ from yuanboot import __version__
14
+ from yuanboot.cli.scaffolding import FRAMEWORK_REPOSITORY, TEMPLATES, create_project
15
+ from yuanboot.configuration import ConfigurationBuilder
16
+ from yuanboot.environment import PROFILES
17
+ from yuanboot.web.application import server_address
18
+
19
+
20
+ def _run_process(command, **options):
21
+ """Forward container/process signals to the managed server before waiting for exit."""
22
+ if os.name == "posix":
23
+ # The CLI forwards terminal signals; isolating the child prevents duplicate SIGINTs.
24
+ options.setdefault("start_new_session", True)
25
+ with subprocess.Popen(command, **options) as process:
26
+ previous = {}
27
+ forwarded_signal = None
28
+
29
+ def forward(signum, frame):
30
+ nonlocal forwarded_signal
31
+ forwarded_signal = signum
32
+ if process.poll() is None:
33
+ process.send_signal(signum)
34
+
35
+ try:
36
+ for sig in (signal.SIGINT, signal.SIGTERM):
37
+ previous[sig] = signal.signal(sig, forward)
38
+ code = process.wait()
39
+ # Uvicorn re-raises a handled signal after its graceful shutdown.
40
+ if forwarded_signal is not None and code == -forwarded_signal:
41
+ return 0
42
+ return code if code >= 0 else 128 - code
43
+ finally:
44
+ for sig, handler in previous.items():
45
+ signal.signal(sig, handler)
46
+
47
+
48
+ def _parser():
49
+ parser = argparse.ArgumentParser(
50
+ prog="yuanbootctl-python", description="yuanboot Python project tools"
51
+ )
52
+ parser.add_argument("--version", action="version", version=f"yuanbootctl-python {__version__}")
53
+ commands = parser.add_subparsers(dest="command", required=True)
54
+ new = commands.add_parser("new", help="Create an application from a template")
55
+ new.add_argument("template", nargs="?")
56
+ new.add_argument("-l", "--list", "--templates", action="store_true", dest="list_templates")
57
+ new.add_argument("-n", "--name", "--projectName", default="demo")
58
+ new.add_argument("-p", "--path", default=".")
59
+ new.add_argument("--framework-repo", default=FRAMEWORK_REPOSITORY)
60
+ new.add_argument("--framework-ref", default="v" + __version__)
61
+ run = commands.add_parser("run", help="Run the current project's web or console host")
62
+ run.add_argument("-p", "--path", default=".")
63
+ run.add_argument("--profile", choices=PROFILES)
64
+ run.add_argument("--host")
65
+ run.add_argument("--port", type=int)
66
+ run.add_argument("--reload", action="store_true")
67
+ run.add_argument("--workers", type=int, default=1)
68
+ run.add_argument("--python", help="Use a Python environment with your project's dependencies")
69
+ build = commands.add_parser("build", help="Build wheel and sdist using python -m build")
70
+ build.add_argument("-p", "--path", default=".")
71
+ build.add_argument("--wheel", action="store_true")
72
+ build.add_argument("--sdist", action="store_true")
73
+ build.add_argument("--outdir")
74
+ build.add_argument("--python", help="Python interpreter with the build package installed")
75
+ commands.add_parser("version", help="Print framework version")
76
+ return parser
77
+
78
+
79
+ def _run(args):
80
+ project = Path(args.path).expanduser().resolve()
81
+ with (project / "pyproject.toml").open("rb") as stream:
82
+ settings = tomllib.load(stream).get("tool", {}).get("yuanboot", {})
83
+ if not settings:
84
+ raise ValueError("Missing [tool.yuanboot] configuration in pyproject.toml")
85
+ env = os.environ.copy()
86
+ if args.profile:
87
+ env["YUANBOOT_PROFILE"] = args.profile
88
+ if args.port is not None:
89
+ env["YUANBOOT_PORT"] = str(args.port)
90
+ if settings.get("kind", "web") == "console":
91
+ if args.reload or args.workers != 1 or args.host or args.port is not None:
92
+ raise ValueError("Console projects do not support web server flags")
93
+ module = settings.get("module")
94
+ if not module:
95
+ raise ValueError("Console projects require tool.yuanboot.module")
96
+ command = [args.python or sys.executable, "-m", module]
97
+ else:
98
+ target = settings.get("app")
99
+ if not target:
100
+ raise ValueError("Web projects require tool.yuanboot.app")
101
+ if args.reload and args.workers != 1:
102
+ raise ValueError("--reload cannot be combined with multiple workers")
103
+ if args.workers < 1:
104
+ raise ValueError("--workers must be positive")
105
+ config = (
106
+ ConfigurationBuilder(project)
107
+ .add_yaml_file(optional=True)
108
+ .add_environment()
109
+ .add_flag_args(["--profile", args.profile] if args.profile else [])
110
+ .build()
111
+ )
112
+ host, port = server_address(config)
113
+ port = args.port if args.port is not None else port
114
+ if not 0 <= port <= 65535:
115
+ raise ValueError("Port must be between 0 and 65535")
116
+ command = [
117
+ args.python or sys.executable,
118
+ "-m",
119
+ "uvicorn",
120
+ target,
121
+ "--host",
122
+ args.host or host,
123
+ "--port",
124
+ str(port),
125
+ "--workers",
126
+ str(args.workers),
127
+ ]
128
+ if args.reload:
129
+ command.append("--reload")
130
+ if settings.get("factory"):
131
+ command.append("--factory")
132
+ tls = config.get_section("yuanboot.application.server.tls")
133
+ for key, flag in (("cert", "--ssl-certfile"), ("key", "--ssl-keyfile")):
134
+ if tls.get_string(key):
135
+ command.extend([flag, tls.get_string(key)])
136
+ if getattr(sys, "frozen", False) and not args.python:
137
+ return _run_embedded(command[2:], project, env)
138
+ with _external_runtime():
139
+ return _run_process(command, cwd=project, env=_external_environment(env))
140
+
141
+
142
+ def _external_environment(env):
143
+ env = env.copy()
144
+ if getattr(sys, "frozen", False):
145
+ for key in ("LD_LIBRARY_PATH", "DYLD_LIBRARY_PATH"):
146
+ original = env.get(key + "_ORIG")
147
+ if original is None:
148
+ env.pop(key, None)
149
+ else:
150
+ env[key] = original
151
+ return env
152
+
153
+
154
+ @contextmanager
155
+ def _external_runtime():
156
+ """Keep bundled shared libraries out of an external Python interpreter's search path."""
157
+ frozen_windows = getattr(sys, "frozen", False) and sys.platform == "win32"
158
+ if frozen_windows:
159
+ import ctypes
160
+
161
+ ctypes.windll.kernel32.SetDllDirectoryW(None)
162
+ try:
163
+ yield
164
+ finally:
165
+ if frozen_windows:
166
+ ctypes.windll.kernel32.SetDllDirectoryW(sys._MEIPASS)
167
+
168
+
169
+ def _run_embedded(arguments, project, env):
170
+ import runpy
171
+
172
+ previous_directory = Path.cwd()
173
+ previous_path, previous_argv = sys.path[:], sys.argv[:]
174
+ previous_environment = os.environ.copy()
175
+ previous_signals = {}
176
+ try:
177
+ os.chdir(project)
178
+ sys.path.insert(0, str(project))
179
+ os.environ.update(env)
180
+ # Uvicorn re-raises signals after shutdown; its outer CLI should exit successfully.
181
+ for sig in (signal.SIGINT, signal.SIGTERM):
182
+ previous_signals[sig] = signal.signal(sig, lambda signum, frame: None)
183
+ if arguments[0] == "uvicorn":
184
+ from uvicorn.main import main as uvicorn_main
185
+
186
+ uvicorn_main(
187
+ args=arguments[1:], prog_name="yuanbootctl-python run", standalone_mode=False
188
+ )
189
+ else:
190
+ sys.argv = [arguments[0]]
191
+ runpy.run_module(arguments[0], run_name="__main__", alter_sys=True)
192
+ return 0
193
+ finally:
194
+ for sig, handler in previous_signals.items():
195
+ signal.signal(sig, handler)
196
+ os.chdir(previous_directory)
197
+ sys.path[:] = previous_path
198
+ sys.argv[:] = previous_argv
199
+ os.environ.clear()
200
+ os.environ.update(previous_environment)
201
+
202
+
203
+ def main(argv: list[str] | None = None) -> int:
204
+ parser = _parser()
205
+ args = parser.parse_args(argv)
206
+ try:
207
+ if args.command == "new":
208
+ if args.list_templates:
209
+ for template in TEMPLATES.values():
210
+ print(f"{template.name:14} {template.description}")
211
+ else:
212
+ if not args.template:
213
+ parser.error("new requires TEMPLATE or --list")
214
+ destination = create_project(
215
+ args.template,
216
+ args.name,
217
+ args.path,
218
+ framework_repo=args.framework_repo,
219
+ framework_ref=args.framework_ref,
220
+ )
221
+ print(f"Created {args.template}: {destination}")
222
+ print(f'cd "{destination}"')
223
+ if not getattr(sys, "frozen", False):
224
+ print('python -m pip install -e ".[dev]"')
225
+ print("yuanbootctl-python run")
226
+ elif args.command == "run":
227
+ return _run(args)
228
+ elif args.command == "build":
229
+ executable = args.python
230
+ if getattr(sys, "frozen", False) and not executable:
231
+ suffix = "Scripts/python.exe" if os.name == "nt" else "bin/python"
232
+ candidate = Path(args.path).expanduser().resolve() / ".venv" / suffix
233
+ if not candidate.is_file():
234
+ raise ValueError(
235
+ "build requires a Python environment: "
236
+ "install build, then use --python /path/to/python"
237
+ )
238
+ executable = str(candidate)
239
+ if not executable and importlib.util.find_spec("build") is None:
240
+ raise ValueError('Install the build tool first: python -m pip install "build>=1.2"')
241
+ command = [executable or sys.executable, "-m", "build"]
242
+ for option in ("wheel", "sdist"):
243
+ if getattr(args, option):
244
+ command.append("--" + option)
245
+ if args.outdir:
246
+ command.extend(["--outdir", args.outdir])
247
+ with _external_runtime():
248
+ return _run_process(
249
+ command,
250
+ cwd=Path(args.path).expanduser(),
251
+ env=_external_environment(os.environ),
252
+ )
253
+ else:
254
+ print(f"yuanbootctl-python {__version__}")
255
+ return 0
256
+ except (OSError, ValueError) as exc:
257
+ print(f"yuanbootctl-python: {exc}", file=sys.stderr)
258
+ return 1
259
+ except KeyboardInterrupt:
260
+ return 130
@@ -0,0 +1,3 @@
1
+ from yuanboot.cli import main
2
+
3
+ raise SystemExit(main())
@@ -0,0 +1,9 @@
1
+ """Entry point for the standalone executable, including multiprocessing workers."""
2
+
3
+ import multiprocessing
4
+
5
+ from yuanboot.cli import main
6
+
7
+ if __name__ == "__main__":
8
+ multiprocessing.freeze_support()
9
+ raise SystemExit(main())
@@ -0,0 +1,119 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import keyword
5
+ import re
6
+ import shutil
7
+ from dataclasses import dataclass
8
+ from importlib.resources import files
9
+ from pathlib import Path
10
+
11
+ from yuanboot import __version__
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class ProjectTemplate:
16
+ name: str
17
+ description: str
18
+ layers: tuple[str, ...]
19
+
20
+
21
+ TEMPLATES = {
22
+ item.name: item
23
+ for item in (
24
+ ProjectTemplate(
25
+ "webapi", "FastAPI endpoints, DI, validation and actuator", ("web", "webapi")
26
+ ),
27
+ ProjectTemplate("simpleweb", "Minimal HTTP application", ("web", "simpleweb")),
28
+ ProjectTemplate("mvc", "Controllers, services and Jinja2 views", ("web", "mvc")),
29
+ ProjectTemplate(
30
+ "microservice",
31
+ "Web API with a managed background worker",
32
+ ("web", "webapi", "microservice"),
33
+ ),
34
+ ProjectTemplate("console", "Console host with managed background services", ("console",)),
35
+ ProjectTemplate("grpc", "gRPC service with protobuf and managed host lifecycle", ("grpc",)),
36
+ )
37
+ }
38
+
39
+ FRAMEWORK_REPOSITORY = "https://github.com/liangboceo/yuanboot-python.git"
40
+
41
+
42
+ def framework_requirement(*, repository=FRAMEWORK_REPOSITORY, ref=None, extras=""):
43
+ if repository.startswith("git@"):
44
+ repository = "ssh://" + repository.replace(":", "/", 1)
45
+ repository = repository.removeprefix("git+")
46
+ if not re.fullmatch(r"(?:ssh|https)://[A-Za-z0-9@._:/-]+", repository):
47
+ raise ValueError("Framework repository must be a Git SSH/HTTPS URL")
48
+ ref = ref or "v" + __version__
49
+ if not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._/-]*", ref) or ".." in ref:
50
+ raise ValueError("Invalid framework Git ref")
51
+ name = f"yuanboot[{extras}]" if extras else "yuanboot"
52
+ return f"{name} @ git+{repository}@{ref}"
53
+
54
+
55
+ def _walk(resource, prefix=Path()):
56
+ for child in sorted(resource.iterdir(), key=lambda p: p.name):
57
+ path = prefix / child.name
58
+ if child.is_dir():
59
+ yield from _walk(child, path)
60
+ elif child.name.endswith(".tpl"):
61
+ yield path.with_suffix(""), child.read_text(encoding="utf-8")
62
+
63
+
64
+ def create_project(
65
+ template: str,
66
+ name: str = "demo",
67
+ path: str | Path = ".",
68
+ *,
69
+ framework_repo: str = FRAMEWORK_REPOSITORY,
70
+ framework_ref: str | None = None,
71
+ ) -> Path:
72
+ if template not in TEMPLATES:
73
+ raise ValueError(f"Unknown template {template!r}. Use 'yuanbootctl-python new --list'.")
74
+ if not re.fullmatch(r"[A-Za-z][A-Za-z0-9_-]{0,63}", name):
75
+ raise ValueError(
76
+ "Project name must start with a letter and contain letters, digits, '-' or '_'"
77
+ )
78
+ package = name.lower().replace("-", "_")
79
+ if keyword.iskeyword(package) or package == "yuanboot":
80
+ raise ValueError(f"Project name {name!r} conflicts with a Python keyword or the framework")
81
+ rendered = {}
82
+ root = files("yuanboot.cli").joinpath("templates")
83
+ substitutions = {
84
+ "__PROJECT__": name,
85
+ "__PACKAGE__": package,
86
+ "__TEMPLATE__": template,
87
+ "__YUANBOOT_VERSION__": __version__,
88
+ "__FRAMEWORK_REF__": framework_ref or "v" + __version__,
89
+ "__KIND__": "console" if template in {"console", "grpc"} else "web",
90
+ "__YUANBOOT_REQUIREMENT__": json.dumps(
91
+ framework_requirement(
92
+ repository=framework_repo,
93
+ ref=framework_ref,
94
+ extras="grpc" if template == "grpc" else "",
95
+ )
96
+ ),
97
+ "__YUANBOOT_COMPONENTS_REQUIREMENT__": json.dumps(
98
+ framework_requirement(repository=framework_repo, ref=framework_ref, extras="all")
99
+ ),
100
+ }
101
+ for layer in ("common", *TEMPLATES[template].layers):
102
+ for relative, content in _walk(root.joinpath(layer)):
103
+ target = str(relative).replace("__PACKAGE__", package)
104
+ for token, value in substitutions.items():
105
+ content = content.replace(token, value)
106
+ rendered[target] = content
107
+ destination = Path(path).expanduser().resolve() / name
108
+ destination.parent.mkdir(parents=True, exist_ok=True)
109
+ # Exclusive creation also rejects existing empty directories and symlinks.
110
+ destination.mkdir(exist_ok=False)
111
+ try:
112
+ for relative, content in rendered.items():
113
+ target = destination / relative
114
+ target.parent.mkdir(parents=True, exist_ok=True)
115
+ target.write_text(content, encoding="utf-8")
116
+ except BaseException:
117
+ shutil.rmtree(destination)
118
+ raise
119
+ return destination
@@ -0,0 +1,7 @@
1
+ .venv
2
+ .git
3
+ __pycache__
4
+ .pytest_cache
5
+ dist
6
+ build
7
+ .env
@@ -0,0 +1,8 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .pytest_cache/
6
+ dist/
7
+ build/
8
+ .env
@@ -0,0 +1,10 @@
1
+ FROM python:3.12-slim
2
+ RUN apt-get update && apt-get install -y --no-install-recommends git \
3
+ && rm -rf /var/lib/apt/lists/*
4
+ WORKDIR /app
5
+ COPY . .
6
+ RUN pip install --no-cache-dir . && useradd --create-home appuser
7
+ USER appuser
8
+ ENV YUANBOOT_PROFILE=pro
9
+ EXPOSE 8080
10
+ CMD ["yuanbootctl-python", "run"]
@@ -0,0 +1,59 @@
1
+ # __PROJECT__
2
+
3
+ 基于 **yuanboot-python** 的 __TEMPLATE__ 项目,Python 3.11+。
4
+
5
+ ## 项目文档
6
+
7
+ | 文档 | 内容 |
8
+ | --- | --- |
9
+ | [yuanboot-python](spec/yuanboot-python.md) | 框架使用说明、配置、依赖注入、MVC 和生命周期 |
10
+ | [核心组件](spec/components.md) | MySQL、Redis、Nacos、日志、服务发现与任务配置 |
11
+ | [后端模块开发规范](spec/backend-module-development.md) | 业务分层、DTO、路由、事务和接口约定 |
12
+
13
+ 生成工具版本:`__YUANBOOT_VERSION__`;框架依赖引用:`__FRAMEWORK_REF__`。
14
+ 文档随项目生成,可离线查阅;后续业务规范可在 `spec` 中补充。
15
+
16
+ ## 快速启动
17
+
18
+ ```bash
19
+ python -m venv .venv
20
+ source .venv/bin/activate
21
+ python -m pip install -e ".[dev]"
22
+ yuanbootctl-python run
23
+ ```
24
+
25
+ 框架通过 `pyproject.toml` 中的 Git tag 依赖安装,无需 PyPI。
26
+ 项目的版本和框架版本各自维护。升级框架时修改 Git tag 并更新依赖 lock。
27
+ 也可将框架依赖改为公开 PyPI 的 `yuanboot==<version>`,组件依赖使用 `yuanboot[all]==<version>`;
28
+ gRPC 项目的基础依赖使用 `yuanboot[grpc]==<version>`,版本应与需要的框架版本一致。
29
+ 私有仓库需要配置 Git SSH/HTTPS 访问权限;默认 Dockerfile 适用于公开 HTTPS 仓库。
30
+ 本地联调先安装完整框架 `pip install -e '/path/to/yuanboot_python[all]'`,
31
+ 再用 `pip install --no-deps -e .` 安装业务代码。
32
+
33
+ 也可用独立二进制直接运行默认模板:`/path/to/yuanbootctl-python run`。
34
+ 项目引入额外依赖后,使用 `run --python /path/to/.venv/bin/python` 指定业务环境。
35
+
36
+ ## 组件与构建
37
+
38
+ 启用全部基础组件:`python -m pip install -e '.[dev,components]'`。
39
+ `main.py` 的 `use_components()` 自动读取 MySQL、Redis、Nacos 注册、日志等配置。
40
+ 配置 `yuanboot.cloud.nacos.data_id` 后,Web/Console 模板会加载 Nacos 配置并监视更新。
41
+
42
+ ```bash
43
+ yuanbootctl-python build
44
+ yuanbootctl-python run --profile pro
45
+ ```
46
+
47
+ Web 模板:`http://127.0.0.1:8080/docs` 查看 API 文档,
48
+ `/actuator/health/readiness` 查看就绪状态。
49
+ Console 模板:运行后台任务,Ctrl+C 或 SIGTERM 优雅停止。
50
+ gRPC 模板:监听 `yuanboot.grpc.address`,默认 127.0.0.1:9090。
51
+
52
+ ## 环境与配置
53
+
54
+ 配置按 config.yml → config_<profile>.yml → YUANBOOT_ 环境变量覆盖。
55
+ 环境固定为 dev / fat / uat / pro;模板包含对应的四份配置,默认 dev。
56
+ 例如 `YUANBOOT_PORT=9000`、`YUANBOOT_GREETING__MESSAGE=hello`。
57
+ `yuanbootctl-python run --port 9000 --reload` 适用于 Web 开发;
58
+ `yuanbootctl-python run --workers 2` 适用于 Web 多进程部署。
59
+ 每个 worker 都会运行自己的后台服务,需要任务唯一性时请使用外部协调机制。
@@ -0,0 +1,11 @@
1
+ yuanboot:
2
+ application:
3
+ name: __PROJECT__
4
+ server:
5
+ address: ":8080"
6
+ logging:
7
+ level: INFO
8
+ json: false
9
+ console: true
10
+ greeting:
11
+ message: "Hello from __PROJECT__"
@@ -0,0 +1,3 @@
1
+ # Loaded over config.yml when YUANBOOT_PROFILE=dev (the default).
2
+ greeting:
3
+ message: "Hello from __PROJECT__ (development)"
@@ -0,0 +1,3 @@
1
+ # Supply secrets through environment variables, never commit credentials.
2
+ greeting:
3
+ message: "Hello from __PROJECT__"
@@ -0,0 +1,3 @@
1
+ # Supply secrets through environment variables, never commit credentials.
2
+ greeting:
3
+ message: "Hello from __PROJECT__"
@@ -0,0 +1,3 @@
1
+ # Supply secrets through environment variables, never commit credentials.
2
+ greeting:
3
+ message: "Hello from __PROJECT__"
@@ -0,0 +1,9 @@
1
+ services:
2
+ app:
3
+ build: .
4
+ ports:
5
+ - "8080:8080"
6
+ environment:
7
+ YUANBOOT_PROFILE: pro
8
+ init: true
9
+ stop_grace_period: 30s
@@ -0,0 +1,26 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.25"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "__PROJECT__"
7
+ version = "0.1.0"
8
+ description = "An application built with yuanboot"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ dependencies = [__YUANBOOT_REQUIREMENT__]
12
+
13
+ [project.optional-dependencies]
14
+ dev = ["build>=1.2"]
15
+ components = [__YUANBOOT_COMPONENTS_REQUIREMENT__]
16
+
17
+ [tool.hatch.build.targets.wheel]
18
+ packages = ["__PACKAGE__"]
19
+
20
+ [tool.hatch.metadata]
21
+ allow-direct-references = true
22
+
23
+ [tool.yuanboot]
24
+ kind = "__KIND__"
25
+ app = "__PACKAGE__.main:app"
26
+ module = "__PACKAGE__.main"