fastapi-modular 0.1.0__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.
- fastapi_modular-0.1.0.dist-info/METADATA +377 -0
- fastapi_modular-0.1.0.dist-info/RECORD +69 -0
- fastapi_modular-0.1.0.dist-info/WHEEL +4 -0
- fastapi_modular-0.1.0.dist-info/entry_points.txt +3 -0
- fastapi_modular-0.1.0.dist-info/licenses/LICENSE +21 -0
- pymodular/__init__.py +74 -0
- pymodular/cli/__init__.py +0 -0
- pymodular/cli/clean.py +39 -0
- pymodular/cli/configure_env.py +569 -0
- pymodular/cli/cong_cu.py +111 -0
- pymodular/cli/info.py +62 -0
- pymodular/cli/install.py +83 -0
- pymodular/cli/main.py +247 -0
- pymodular/cli/new_module.py +492 -0
- pymodular/cli/new_project.py +471 -0
- pymodular/cli/serve.py +59 -0
- pymodular/core/__init__.py +0 -0
- pymodular/core/clock.py +15 -0
- pymodular/core/compat.py +39 -0
- pymodular/core/config.py +495 -0
- pymodular/core/container.py +354 -0
- pymodular/core/context.py +78 -0
- pymodular/core/controller.py +208 -0
- pymodular/core/error_handlers.py +272 -0
- pymodular/core/exceptions.py +104 -0
- pymodular/core/guards.py +117 -0
- pymodular/core/lifespan.py +150 -0
- pymodular/core/logging.py +88 -0
- pymodular/core/metrics.py +190 -0
- pymodular/core/schemas.py +105 -0
- pymodular/core/websocket/__init__.py +31 -0
- pymodular/core/websocket/adapter.py +192 -0
- pymodular/core/websocket/gateway.py +735 -0
- pymodular/core/websocket/namespace.py +148 -0
- pymodular/core/websocket/protocol.py +157 -0
- pymodular/core/websocket/server.py +175 -0
- pymodular/core/websocket/socket.py +241 -0
- pymodular/discovery.py +180 -0
- pymodular/factory.py +126 -0
- pymodular/infrastructure/__init__.py +1 -0
- pymodular/infrastructure/database/__init__.py +8 -0
- pymodular/infrastructure/database/base.py +228 -0
- pymodular/infrastructure/database/circuit.py +207 -0
- pymodular/infrastructure/database/factory.py +88 -0
- pymodular/infrastructure/database/memory.py +112 -0
- pymodular/infrastructure/database/mongo.py +186 -0
- pymodular/infrastructure/database/repository.py +188 -0
- pymodular/infrastructure/database/sql.py +520 -0
- pymodular/infrastructure/kafka/__init__.py +26 -0
- pymodular/infrastructure/kafka/broker.py +231 -0
- pymodular/infrastructure/kafka/consumers.py +371 -0
- pymodular/infrastructure/kafka/metrics.py +17 -0
- pymodular/infrastructure/mqtt/__init__.py +35 -0
- pymodular/infrastructure/mqtt/client.py +292 -0
- pymodular/infrastructure/mqtt/consumers.py +219 -0
- pymodular/infrastructure/mqtt/metrics.py +17 -0
- pymodular/infrastructure/mqtt/patterns.py +116 -0
- pymodular/infrastructure/rabbitmq/__init__.py +33 -0
- pymodular/infrastructure/rabbitmq/broker.py +616 -0
- pymodular/infrastructure/rabbitmq/consumers.py +450 -0
- pymodular/infrastructure/rabbitmq/metrics.py +34 -0
- pymodular/infrastructure/rabbitmq/patterns.py +64 -0
- pymodular/infrastructure/redis/__init__.py +31 -0
- pymodular/infrastructure/redis/client.py +362 -0
- pymodular/infrastructure/redis/metrics.py +20 -0
- pymodular/infrastructure/redis/pubsub.py +262 -0
- pymodular/middleware/__init__.py +0 -0
- pymodular/middleware/request_context.py +164 -0
- pymodular/py.typed +0 -0
pymodular/cli/info.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""`pym info` — đang cấu hình những gì, và thư viện nào đã cài.
|
|
2
|
+
|
|
3
|
+
Gộp sáu lệnh `*-info` cũ của Makefile vào một chỗ, vì câu hỏi thật sự luôn là "hiện tại
|
|
4
|
+
app của tôi đang nối vào đâu" chứ không phải "riêng RabbitMQ thế nào".
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import importlib.util
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _co(ten: str) -> str:
|
|
13
|
+
return "có" if importlib.util.find_spec(ten) else "chưa cài"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _dong(nhan: str, gia_tri: object) -> None:
|
|
17
|
+
print(f" {nhan:<16}{gia_tri}")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def info() -> int:
|
|
21
|
+
from pymodular import __version__
|
|
22
|
+
from pymodular.core.config import get_settings
|
|
23
|
+
from pymodular.infrastructure.mqtt.client import safe_url as mqtt_url
|
|
24
|
+
from pymodular.infrastructure.rabbitmq.broker import safe_url as amqp_url
|
|
25
|
+
from pymodular.infrastructure.redis.client import safe_url as redis_url
|
|
26
|
+
|
|
27
|
+
s = get_settings()
|
|
28
|
+
|
|
29
|
+
print(f"pymodular {__version__} · {type(s).__name__}")
|
|
30
|
+
print("\nỨng dụng")
|
|
31
|
+
_dong("name", s.name)
|
|
32
|
+
_dong("env", s.env)
|
|
33
|
+
_dong("host:port", f"{s.host}:{s.port}")
|
|
34
|
+
|
|
35
|
+
print("\nDatabase")
|
|
36
|
+
_dong("driver", s.db.driver)
|
|
37
|
+
_dong("dsn", s.db.resolved_dsn or "(bộ nhớ tạm — mất khi restart)")
|
|
38
|
+
_dong("schema_mode", s.db.schema_mode)
|
|
39
|
+
_dong("thư viện", f"sqlalchemy: {_co('sqlalchemy')} · asyncpg: {_co('asyncpg')} · "
|
|
40
|
+
f"aiosqlite: {_co('aiosqlite')} · motor: {_co('motor')}")
|
|
41
|
+
|
|
42
|
+
print("\nWebSocket")
|
|
43
|
+
_dong("adapter", s.ws.adapter + ("" if s.ws.adapter == "redis" else " (chỉ đúng với MỘT worker)"))
|
|
44
|
+
if s.ws.adapter == "redis":
|
|
45
|
+
_dong("redis_url", s.ws.redis_url)
|
|
46
|
+
_dong("nhịp tim", f"{s.ws.heartbeat_seconds}s / im lặng tối đa {s.ws.idle_timeout_seconds}s")
|
|
47
|
+
|
|
48
|
+
print("\nHạ tầng tuỳ chọn")
|
|
49
|
+
for nhan, bat, dia_chi, thu_vien in (
|
|
50
|
+
("rabbitmq", s.rabbitmq.enabled, amqp_url(s.rabbitmq.url), "aio_pika"),
|
|
51
|
+
("redis", s.redis.enabled, redis_url(s.redis.url), "redis"),
|
|
52
|
+
("mqtt", s.mqtt.enabled, mqtt_url(s.mqtt.url), "aiomqtt"),
|
|
53
|
+
("kafka", s.kafka.enabled, s.kafka.bootstrap_servers, "aiokafka"),
|
|
54
|
+
):
|
|
55
|
+
trang_thai = f"bật {dia_chi}" if bat else "tắt"
|
|
56
|
+
_dong(nhan, f"{trang_thai:<45}thư viện: {_co(thu_vien)}")
|
|
57
|
+
|
|
58
|
+
if canh_bao := s.check_production_safety():
|
|
59
|
+
print("\nCẢNH BÁO cho môi trường prod")
|
|
60
|
+
for dong in canh_bao:
|
|
61
|
+
print(f" - {dong}")
|
|
62
|
+
return 0
|
pymodular/cli/install.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""`pym install <thành-phần>` — cài thư viện của một thành phần rồi ghi .env.
|
|
2
|
+
|
|
3
|
+
Hai việc luôn đi cùng nhau nên gộp làm một: cài `aio-pika` mà quên ghi
|
|
4
|
+
`APP_RABBITMQ__*` thì lớp đó vẫn nằm im, còn ghi biến mà chưa cài thư viện thì
|
|
5
|
+
app báo `ComponentNotEnabledError` lúc khởi động.
|
|
6
|
+
|
|
7
|
+
Cài THẲNG các gói phụ thuộc chứ không chạy `pip install "fastapi-modular[x]"`. Lý do
|
|
8
|
+
rất thực tế: cách sau bắt pip đi tìm chính pymodular trên PyPI, nên hỏng ngay
|
|
9
|
+
khi bạn đang dùng bản cài từ file .whl, bản `pip install -e .`, hay bản chưa
|
|
10
|
+
phát hành.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import subprocess
|
|
16
|
+
import sys
|
|
17
|
+
|
|
18
|
+
# Nguồn sự thật là `[project.optional-dependencies]` trong pyproject.toml.
|
|
19
|
+
# `test_extras_khop_pyproject` giữ hai chỗ này không lệch nhau.
|
|
20
|
+
GOI: dict[str, list[str]] = {
|
|
21
|
+
"sqlite": ["sqlalchemy[asyncio]>=2.0.30,<3.0.0", "aiosqlite>=0.20.0", "alembic>=1.13.0"],
|
|
22
|
+
"postgres": ["sqlalchemy[asyncio]>=2.0.30,<3.0.0", "asyncpg>=0.29.0", "alembic>=1.13.0"],
|
|
23
|
+
"mongodb": ["motor>=3.6.0,<4.0.0"],
|
|
24
|
+
"rabbitmq": ["aio-pika>=9.4.0,<10.0.0"],
|
|
25
|
+
"redis": ["redis>=5.0.0,<7.0.0"],
|
|
26
|
+
"mqtt": ["aiomqtt>=2.0.0,<3.0.0"],
|
|
27
|
+
"kafka": ["aiokafka>=0.10.0,<0.13.0"],
|
|
28
|
+
"dev": ["pytest>=8.3.0", "pytest-asyncio>=0.24.0", "httpx>=0.27.0", "ruff>=0.6.0"],
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
# Thành phần -> khối .env tương ứng. `ws-redis` dùng chung thư viện với `redis`
|
|
32
|
+
# nhưng ghi khối cấu hình khác: một bên là lớp cache/pub-sub, một bên là adapter
|
|
33
|
+
# phát tin WebSocket xuyên worker.
|
|
34
|
+
KHOI_ENV: dict[str, str] = {
|
|
35
|
+
"sqlite": "sqlite",
|
|
36
|
+
"postgres": "postgres",
|
|
37
|
+
"mongodb": "mongodb",
|
|
38
|
+
"rabbitmq": "rabbitmq",
|
|
39
|
+
"redis": "redis",
|
|
40
|
+
"mqtt": "mqtt",
|
|
41
|
+
"kafka": "kafka",
|
|
42
|
+
"ws-redis": "ws-redis",
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
_ALIAS = {"mongo": "mongodb", "ws-redis": "redis", "postgresql": "postgres"}
|
|
46
|
+
|
|
47
|
+
THANH_PHAN = sorted({*GOI, *KHOI_ENV, "all"})
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def install(ten: str, *, ghi_env: bool = True, env_file: object = None) -> int:
|
|
51
|
+
from pathlib import Path
|
|
52
|
+
|
|
53
|
+
if ten not in THANH_PHAN:
|
|
54
|
+
print(f"Không biết thành phần {ten!r}. Chọn một trong: {', '.join(THANH_PHAN)}")
|
|
55
|
+
return 1
|
|
56
|
+
|
|
57
|
+
goi_can = (
|
|
58
|
+
sorted({g for k, v in GOI.items() if k != "dev" for g in v})
|
|
59
|
+
if ten == "all"
|
|
60
|
+
else GOI[_ALIAS.get(ten, ten)]
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
print(f"Cài {len(goi_can)} gói cho '{ten}':")
|
|
64
|
+
for g in goi_can:
|
|
65
|
+
print(f" {g}")
|
|
66
|
+
ma = subprocess.call([sys.executable, "-m", "pip", "install", *goi_can])
|
|
67
|
+
if ma != 0:
|
|
68
|
+
print(
|
|
69
|
+
f"\npip thoát với mã {ma}. Cài tay:\n"
|
|
70
|
+
f" {sys.executable} -m pip install {' '.join(goi_can)}"
|
|
71
|
+
)
|
|
72
|
+
return ma
|
|
73
|
+
|
|
74
|
+
khoi = KHOI_ENV.get(ten)
|
|
75
|
+
if not ghi_env or khoi is None:
|
|
76
|
+
if khoi is None and ghi_env:
|
|
77
|
+
print(f"\n'{ten}' không có biến cấu hình riêng — không ghi .env.")
|
|
78
|
+
return 0
|
|
79
|
+
|
|
80
|
+
from pymodular.cli.configure_env import main as ghi
|
|
81
|
+
|
|
82
|
+
print()
|
|
83
|
+
return ghi(khoi, Path(env_file) if env_file else Path(".env"))
|
pymodular/cli/main.py
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"""Điểm vào của lệnh `pymodular`, gõ tắt là `pym`.
|
|
2
|
+
|
|
3
|
+
pym init dựng dự án ngay trong THƯ MỤC HIỆN TẠI
|
|
4
|
+
pym new duan-cua-toi dựng dự án trong một thư mục mới
|
|
5
|
+
pym dev chạy kèm autoreload
|
|
6
|
+
pym run --workers 4 chạy chế độ production
|
|
7
|
+
pym module alerts sinh module nghiệp vụ
|
|
8
|
+
pym install postgres cài thư viện của một thành phần + ghi .env
|
|
9
|
+
pym env postgres chỉ ghi biến cấu hình vào .env
|
|
10
|
+
pym info đang nối vào đâu, thư viện nào đã cài
|
|
11
|
+
pym migrate chạy migration (Alembic)
|
|
12
|
+
pym test / pym lint chạy test / soi lỗi tĩnh
|
|
13
|
+
|
|
14
|
+
Hai tên gọi cùng một chương trình; `pymodular` là tên đầy đủ cho script và tài
|
|
15
|
+
liệu, `pym` là để gõ hằng ngày.
|
|
16
|
+
|
|
17
|
+
Lệnh và tham số dạng danh sách đều rút gọn được, miễn là KHÔNG NHẬP NHẰNG:
|
|
18
|
+
|
|
19
|
+
pym mo alerts = pym module alerts
|
|
20
|
+
pym ins sq = pym install sqlite
|
|
21
|
+
pym d = pym dev
|
|
22
|
+
|
|
23
|
+
`pym m` thì báo lỗi kèm gợi ý, vì cả `module` lẫn `migrate` đều bắt đầu bằng "m"
|
|
24
|
+
— thà hỏi lại còn hơn đoán bừa rồi chạy nhầm lệnh.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import argparse
|
|
30
|
+
import os
|
|
31
|
+
import sys
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
|
|
34
|
+
from pymodular import __version__
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def giai_nghia(tu: str, lua_chon: list[str], nhan: str) -> str:
|
|
38
|
+
"""Đổi một từ viết tắt thành lựa chọn đầy đủ, nếu chỉ có đúng một khả năng.
|
|
39
|
+
|
|
40
|
+
Trả về nguyên `tu` khi nó đã là lựa chọn đầy đủ hoặc không khớp gì — để
|
|
41
|
+
argparse tự báo lỗi theo cách của nó. Chỉ ném lỗi khi NHẬP NHẰNG, vì đó là
|
|
42
|
+
trường hợp duy nhất mà im lặng đoán bừa sẽ chạy nhầm việc.
|
|
43
|
+
"""
|
|
44
|
+
if tu in lua_chon:
|
|
45
|
+
return tu
|
|
46
|
+
khop = [c for c in lua_chon if c.startswith(tu)]
|
|
47
|
+
if len(khop) == 1:
|
|
48
|
+
return khop[0]
|
|
49
|
+
if len(khop) > 1:
|
|
50
|
+
raise SystemExit(
|
|
51
|
+
f"pym: {nhan} {tu!r} chưa rõ — khớp với {', '.join(sorted(khop))}. "
|
|
52
|
+
"Gõ thêm vài chữ cho rõ."
|
|
53
|
+
)
|
|
54
|
+
return tu
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _mo_rong_vietat(argv: list[str], lenh: list[str]) -> list[str]:
|
|
58
|
+
"""Mở rộng từ viết tắt ở vị trí TÊN LỆNH, và ở tham số dạng danh sách."""
|
|
59
|
+
vi_tri = next((i for i, t in enumerate(argv) if not t.startswith("-")), None)
|
|
60
|
+
if vi_tri is None:
|
|
61
|
+
return argv
|
|
62
|
+
|
|
63
|
+
argv = list(argv)
|
|
64
|
+
argv[vi_tri] = giai_nghia(argv[vi_tri], lenh, "lệnh")
|
|
65
|
+
|
|
66
|
+
ke_tiep = next(
|
|
67
|
+
(i for i in range(vi_tri + 1, len(argv)) if not argv[i].startswith("-")), None
|
|
68
|
+
)
|
|
69
|
+
if ke_tiep is None:
|
|
70
|
+
return argv
|
|
71
|
+
|
|
72
|
+
if argv[vi_tri] in ("install", "env"):
|
|
73
|
+
from pymodular.cli.configure_env import BLOCKS
|
|
74
|
+
from pymodular.cli.install import THANH_PHAN
|
|
75
|
+
|
|
76
|
+
chon = THANH_PHAN if argv[vi_tri] == "install" else sorted(BLOCKS)
|
|
77
|
+
argv[ke_tiep] = giai_nghia(argv[ke_tiep], chon, "thành phần")
|
|
78
|
+
elif argv[vi_tri] == "migrate":
|
|
79
|
+
argv[ke_tiep] = giai_nghia(
|
|
80
|
+
argv[ke_tiep], ["up", "down", "history", "sql", "create"], "việc"
|
|
81
|
+
)
|
|
82
|
+
return argv
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def main(argv: list[str] | None = None) -> int:
|
|
86
|
+
"""Điểm vào thật. Bọc `_main` để `pym info | head` không văng traceback."""
|
|
87
|
+
try:
|
|
88
|
+
return _main(argv)
|
|
89
|
+
except BrokenPipeError:
|
|
90
|
+
# Người ta nối vào `head`/`less` rồi thoát sớm: đó là chuyện bình
|
|
91
|
+
# thường, không phải lỗi. Trỏ stdout vào /dev/null để lúc Python dọn
|
|
92
|
+
# dẹp không cố ghi thêm lần nữa.
|
|
93
|
+
os.dup2(os.open(os.devnull, os.O_WRONLY), sys.stdout.fileno())
|
|
94
|
+
return 0
|
|
95
|
+
except KeyboardInterrupt:
|
|
96
|
+
return 130
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _main(argv: list[str] | None = None) -> int:
|
|
100
|
+
parser = argparse.ArgumentParser(
|
|
101
|
+
prog="pym",
|
|
102
|
+
description="FastAPI theo kiến trúc module kiểu NestJS (gõ đầy đủ: pymodular)",
|
|
103
|
+
)
|
|
104
|
+
parser.add_argument("--version", action="version", version=f"pymodular {__version__}")
|
|
105
|
+
lenh = parser.add_subparsers(dest="lenh", required=True)
|
|
106
|
+
|
|
107
|
+
p_init = lenh.add_parser(
|
|
108
|
+
"init", help="dựng dự án ngay trong thư mục hiện tại (không tạo thêm cấp)"
|
|
109
|
+
)
|
|
110
|
+
p_init.add_argument("--name", help="tên dự án; mặc định lấy theo tên thư mục")
|
|
111
|
+
p_init.add_argument("--root", type=Path, default=Path("."), help="thư mục đích")
|
|
112
|
+
|
|
113
|
+
p_new = lenh.add_parser("new", help="dựng dự án trong một thư mục MỚI")
|
|
114
|
+
p_new.add_argument("ten", help="tên thư mục dự án")
|
|
115
|
+
p_new.add_argument("--root", type=Path, default=Path("."), help="tạo ở đâu (mặc định: .)")
|
|
116
|
+
|
|
117
|
+
p_mod = lenh.add_parser("module", help="sinh module nghiệp vụ")
|
|
118
|
+
p_mod.add_argument("ten", help="tên module, dạng số nhiều viết thường: alerts")
|
|
119
|
+
p_mod.add_argument("--entity", help="tên entity dạng số ít; mặc định đoán từ tên module")
|
|
120
|
+
p_mod.add_argument(
|
|
121
|
+
"--root", type=Path, default=Path("src/api"), help="thư mục chứa các module"
|
|
122
|
+
)
|
|
123
|
+
p_mod.add_argument("--gateway", action="store_true", help="tạo kèm gateway WebSocket")
|
|
124
|
+
p_mod.add_argument("--gateway-only", action="store_true")
|
|
125
|
+
p_mod.add_argument("--consumer", action="store_true", help="tạo kèm consumer RabbitMQ")
|
|
126
|
+
p_mod.add_argument("--consumer-only", action="store_true")
|
|
127
|
+
|
|
128
|
+
p_dev = lenh.add_parser("dev", help="chạy kèm autoreload")
|
|
129
|
+
p_run = lenh.add_parser("run", help="chạy chế độ production, nhiều worker")
|
|
130
|
+
for p_chay in (p_dev, p_run):
|
|
131
|
+
p_chay.add_argument("--app", default="src.main:app", help="điểm vào ASGI")
|
|
132
|
+
p_chay.add_argument("--host", help="mặc định lấy APP_HOST")
|
|
133
|
+
p_chay.add_argument("--port", type=int, help="mặc định lấy APP_PORT")
|
|
134
|
+
p_run.add_argument("--workers", type=int, default=4)
|
|
135
|
+
|
|
136
|
+
lenh.add_parser("info", help="cấu hình đang dùng và thư viện đã cài")
|
|
137
|
+
|
|
138
|
+
p_test = lenh.add_parser("test", help="chạy pytest")
|
|
139
|
+
p_test.add_argument("them", nargs="*", help="tham số truyền thẳng cho pytest")
|
|
140
|
+
|
|
141
|
+
p_lint = lenh.add_parser("lint", help="soi lỗi tĩnh bằng ruff")
|
|
142
|
+
p_lint.add_argument("--fix", action="store_true", help="tự sửa những lỗi sửa được")
|
|
143
|
+
p_lint.add_argument("duong_dan", nargs="*", default=["src"], help="mặc định: src")
|
|
144
|
+
|
|
145
|
+
p_mig = lenh.add_parser("migrate", help="chạy migration (Alembic)")
|
|
146
|
+
p_mig.add_argument(
|
|
147
|
+
"viec",
|
|
148
|
+
nargs="?",
|
|
149
|
+
default="up",
|
|
150
|
+
choices=["up", "down", "history", "sql", "create"],
|
|
151
|
+
help="up (mặc định) | down | history | sql | create",
|
|
152
|
+
)
|
|
153
|
+
p_mig.add_argument("-m", "--message", help="mô tả, dùng với `create`")
|
|
154
|
+
|
|
155
|
+
from pymodular.cli.install import THANH_PHAN
|
|
156
|
+
|
|
157
|
+
p_ins = lenh.add_parser(
|
|
158
|
+
"install", help="cài thư viện của một thành phần rồi ghi biến vào .env"
|
|
159
|
+
)
|
|
160
|
+
p_ins.add_argument("thanh_phan", choices=THANH_PHAN, metavar="thành-phần",
|
|
161
|
+
help=" | ".join(THANH_PHAN))
|
|
162
|
+
p_ins.add_argument("--no-env", action="store_true", help="chỉ cài, đừng đụng .env")
|
|
163
|
+
p_ins.add_argument("--file", type=Path, default=Path(".env"))
|
|
164
|
+
|
|
165
|
+
lenh.add_parser("clean", help="xoá cache và bản dựng (không đụng dữ liệu)")
|
|
166
|
+
|
|
167
|
+
p_build = lenh.add_parser("build", help="dựng wheel + sdist vào dist/")
|
|
168
|
+
p_build.add_argument("--no-clean", action="store_true", help="giữ dist/ cũ")
|
|
169
|
+
|
|
170
|
+
p_pub = lenh.add_parser("publish", help="đẩy gói lên PyPI")
|
|
171
|
+
p_pub.add_argument("--test", action="store_true", help="đẩy lên TestPyPI trước")
|
|
172
|
+
|
|
173
|
+
p_env = lenh.add_parser("env", help="ghi biến cấu hình của một thành phần vào .env")
|
|
174
|
+
p_env.add_argument("thanh_phan", help="sqlite | postgres | mongodb | redis | "
|
|
175
|
+
"rabbitmq | mqtt | kafka | ws-redis")
|
|
176
|
+
p_env.add_argument("--file", type=Path, default=Path(".env"))
|
|
177
|
+
|
|
178
|
+
args = parser.parse_args(
|
|
179
|
+
_mo_rong_vietat(list(argv if argv is not None else sys.argv[1:]), list(lenh.choices))
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# Import muộn: `pymodular new` không cần kéo theo bộ sinh module, và
|
|
183
|
+
# `--version` thì không cần kéo theo gì cả.
|
|
184
|
+
if args.lenh == "init":
|
|
185
|
+
from pymodular.cli.new_project import init_du_an
|
|
186
|
+
|
|
187
|
+
return init_du_an(args.root, args.name)
|
|
188
|
+
|
|
189
|
+
if args.lenh == "new":
|
|
190
|
+
from pymodular.cli.new_project import tao_du_an
|
|
191
|
+
|
|
192
|
+
return tao_du_an(args.ten, args.root)
|
|
193
|
+
|
|
194
|
+
if args.lenh == "module":
|
|
195
|
+
from pymodular.cli.new_module import main as sinh_module
|
|
196
|
+
|
|
197
|
+
argv2 = [args.ten, "--root", str(args.root)]
|
|
198
|
+
if args.entity:
|
|
199
|
+
argv2 += ["--entity", args.entity]
|
|
200
|
+
for co, ten_co in (
|
|
201
|
+
(args.gateway, "--gateway"),
|
|
202
|
+
(args.gateway_only, "--gateway-only"),
|
|
203
|
+
(args.consumer, "--consumer"),
|
|
204
|
+
(args.consumer_only, "--consumer-only"),
|
|
205
|
+
):
|
|
206
|
+
if co:
|
|
207
|
+
argv2.append(ten_co)
|
|
208
|
+
return sinh_module(argv2)
|
|
209
|
+
|
|
210
|
+
if args.lenh in ("dev", "run"):
|
|
211
|
+
from pymodular.cli.serve import serve
|
|
212
|
+
|
|
213
|
+
return serve(
|
|
214
|
+
target=args.app,
|
|
215
|
+
reload=args.lenh == "dev",
|
|
216
|
+
workers=getattr(args, "workers", None),
|
|
217
|
+
host=args.host,
|
|
218
|
+
port=args.port,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
if args.lenh == "info":
|
|
222
|
+
from pymodular.cli.info import info
|
|
223
|
+
|
|
224
|
+
return info()
|
|
225
|
+
|
|
226
|
+
if args.lenh == "install":
|
|
227
|
+
from pymodular.cli.install import install
|
|
228
|
+
|
|
229
|
+
return install(args.thanh_phan, ghi_env=not args.no_env, env_file=args.file)
|
|
230
|
+
|
|
231
|
+
if args.lenh == "clean":
|
|
232
|
+
from pymodular.cli.clean import clean
|
|
233
|
+
|
|
234
|
+
return clean()
|
|
235
|
+
|
|
236
|
+
if args.lenh in ("test", "lint", "migrate", "build", "publish"):
|
|
237
|
+
from pymodular.cli.cong_cu import chay_cong_cu
|
|
238
|
+
|
|
239
|
+
return chay_cong_cu(args)
|
|
240
|
+
|
|
241
|
+
from pymodular.cli.configure_env import main as ghi_env
|
|
242
|
+
|
|
243
|
+
return ghi_env(args.thanh_phan, args.file)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
if __name__ == "__main__":
|
|
247
|
+
raise SystemExit(main())
|