πŸ“¦ release: crates.io publish prep β€” metadata, versions on path-deps, bin renamed to justapi-cli

swadhinbiswas committed 2 months ago
f47a45e
1 parent 74e99fc
commit f47a45e
5 files changed
+170 -18
β–ΌCargo.toml
+2-0
13
13
version = "2.0.9"
14
14
edition = "2021"
15
15
license = "MIT"
16
+description = "JustAPI β€” Python web framework with a Rust core. Networking, TLS, routing, validation, serialization run in Rust."
17
+repository = "https://github.com/swadhinbiswas/JustAPI"
16
18
17
19
# Release builds abort on panic instead of unwinding. This is required for the
18
20
# pyo3 extension-module FFI boundary: a Rust panic that unwinds across the
19
21
β–Όcrates/justapi-cli/Cargo.toml
+3-2
1
1
[package]
2
2
name = "justapi-cli"
3
+description = "JustAPI CLI β€” serve, create, check, profile, openapi, hot reload. High-performance Rust CLI for the JustAPI framework."
3
4
version.workspace = true
4
5
edition.workspace = true
5
6
license.workspace = true
6
7
7
8
[[bin]]
8
-name = "justapi"
9
+name = "justapi-cli"
9
10
path = "src/main.rs"
10
11
11
12
[features]
17
18
18
19
[dependencies]
19
20
justapi-core = { workspace = true, features = ["db"] }
20
-justapi-inference = { path = "../justapi-inference", optional = true }
21
+justapi-inference = { version = "2.0.9", path = "../justapi-inference", optional = true }
21
22
tokio.workspace = true
22
23
clap.workspace = true
23
24
tracing.workspace = true
24
25
β–Όcrates/justapi-core/Cargo.toml
+2-1
1
1
[package]
2
2
name = "justapi-core"
3
+description = "JustAPI core β€” tokio/hyper networking, matchit router, middleware, validation, serialization, sqlx DB pool."
3
4
version.workspace = true
4
5
edition.workspace = true
5
6
license.workspace = true
75
76
notify = "8.2.0"
76
77
arc-swap = "1.9.2"
77
78
async-graphql = { version = "7.2.1", optional = true }
78
-justapi-inference = { path = "../justapi-inference", optional = true }
79
+justapi-inference = { version = "2.0.9", path = "../justapi-inference", optional = true }
79
80
tempfile = "3.27.0"
80
81
lettre = { workspace = true, optional = true }
81
82
minijinja = { workspace = true, optional = true }
82
83
β–Όcrates/justapi-inference/Cargo.toml
+1-0
1
1
[package]
2
2
name = "justapi-inference"
3
+description = "JustAPI inference engine β€” Candle-based LLM serving (experimental, optional feature)."
3
4
version.workspace = true
4
5
edition.workspace = true
5
6
license.workspace = true
6
7
β–Όcrates/justapi-py/python/justapi/cli.py
+162-15
1
-import sys
1
+"""JustAPI CLI β€” Python package entry point.
2
+
3
+Provides the core commands standalone (no Rust binary required) and
4
+transparently delegates to the high-performance Rust CLI (`justapi-cli`)
5
+when it is installed via `cargo install justapi-cli`.
6
+
7
+Commands:
8
+ justapi --version Show version
9
+ justapi serve [addr] Run the app in the current directory (main.py / app.py)
10
+ justapi create NAME Scaffold a new project
11
+ justapi check [file] Validate routes without starting the server
12
+ justapi openapi [file] Print the OpenAPI spec
13
+"""
14
+
15
+import argparse
16
+import importlib.util
17
+import os
2
18
import subprocess
19
+import sys
3
20
import shutil
4
21
5
-def main():
6
- """JustAPI CLI entrypoint for python package."""
7
- binary = shutil.which("justapi-cli")
8
- if binary:
9
- sys.exit(subprocess.call([binary] + sys.argv[1:]))
22
+__version__ = "2.0.9"
23
+
24
+RUST_BINARY = "justapi-cli" # cargo install justapi-cli β†’ binary name
25
+
26
+
27
+def _rust_cli() -> str | None:
28
+ """Return the path to the Rust CLI if installed, else None."""
29
+ return shutil.which(RUST_BINARY)
30
+
31
+
32
+def _delegate_or(args: list[str]) -> bool:
33
+ """If the Rust CLI is installed, exec it with args. Returns True if delegated."""
34
+ rust = _rust_cli()
35
+ if rust:
36
+ sys.exit(subprocess.call([rust] + args))
37
+ return False
38
+
39
+
40
+def _find_app_file() -> str | None:
41
+ """Locate the user's app module: app.py, main.py, or src/app.py."""
42
+ for name in ("app.py", "main.py", "src/app.py"):
43
+ if os.path.isfile(name):
44
+ return name
45
+ return None
46
+
47
+
48
+def cmd_version(args: argparse.Namespace) -> None:
49
+ rust = _rust_cli()
50
+ print(f"justapi {__version__} (Python package)")
51
+ if rust:
52
+ print(f"Rust CLI detected: {rust}")
53
+ print(f"Python {sys.version.split()[0]} Β· PyPI package")
54
+
55
+
56
+def cmd_serve(args: argparse.Namespace) -> None:
57
+ """Run the app in the current directory."""
58
+ if _delegate_or([f"--addr={args.addr}"]):
59
+ return
60
+ app_file = _find_app_file()
61
+ if app_file is None:
62
+ print("error: no app.py or main.py found in the current directory", file=sys.stderr)
63
+ sys.exit(1)
64
+ print(f"justapi {__version__} β€” serving {app_file} on {args.addr}")
65
+ # Import the app module β€” its `app.run(...)` (or `if __name__`) starts the server.
66
+ module_name = os.path.splitext(app_file)[0].replace("/", ".")
67
+ spec = importlib.util.spec_from_file_location(module_name, app_file)
68
+ assert spec and spec.loader
69
+ module = importlib.util.module_from_spec(spec)
70
+ sys.argv = [app_file, args.addr]
71
+ spec.loader.exec_module(module)
72
+ # If the module only defines `app` without calling run(), call it for the user.
73
+ app = getattr(module, "app", None)
74
+ if app is not None and not getattr(module, "_justapi_ran", False):
75
+ app.run(args.addr)
76
+
10
77
11
- if len(sys.argv) > 1 and sys.argv[1] in ("--version", "-V"):
12
- print("justapi 2.0.0")
78
+def cmd_create(args: argparse.Namespace) -> None:
79
+ """Scaffold a new project. Delegates to Rust CLI when available."""
80
+ if _delegate_or(["create", args.name] + args.rest):
13
81
return
82
+ print("error: `justapi create` requires the Rust CLI:", file=sys.stderr)
83
+ print(" cargo install justapi-cli", file=sys.stderr)
84
+ sys.exit(1)
85
+
86
+
87
+def cmd_check(args: argparse.Namespace) -> None:
88
+ """Validate the app's routes without starting the server."""
89
+ if _delegate_or(["check"] + ([args.file] if args.file else [])):
90
+ return
91
+ app_file = args.file or _find_app_file()
92
+ if app_file is None:
93
+ print("error: no app.py or main.py found", file=sys.stderr)
94
+ sys.exit(1)
95
+ spec = importlib.util.spec_from_file_location("app", app_file)
96
+ assert spec and spec.loader
97
+ module = importlib.util.module_from_spec(spec)
98
+ spec.loader.exec_module(module)
99
+ app = getattr(module, "app", None)
100
+ if app is None:
101
+ print(f"error: {app_file} does not define `app`", file=sys.stderr)
102
+ sys.exit(1)
103
+ print(f"βœ“ {app_file}: app loaded, routes registered")
104
+
105
+
106
+def cmd_openapi(args: argparse.Namespace) -> None:
107
+ """Print the OpenAPI spec for the app."""
108
+ if _delegate_or(["openapi"] + ([args.file] if args.file else [])):
109
+ return
110
+ app_file = args.file or _find_app_file()
111
+ if app_file is None:
112
+ print("error: no app.py or main.py found", file=sys.stderr)
113
+ sys.exit(1)
114
+ spec = importlib.util.spec_from_file_location("app", app_file)
115
+ assert spec and spec.loader
116
+ module = importlib.util.module_from_spec(spec)
117
+ spec.loader.exec_module(module)
118
+ app = getattr(module, "app", None)
119
+ if app is None:
120
+ print(f"error: {app_file} does not define `app`", file=sys.stderr)
121
+ sys.exit(1)
122
+ import json
123
+ spec_dict = app.openapi_spec() if hasattr(app, "openapi_spec") else {}
124
+ print(json.dumps(spec_dict, indent=2))
125
+
126
+
127
+def main(argv: list[str] | None = None) -> None:
128
+ argv = argv if argv is not None else sys.argv[1:]
129
+ parser = argparse.ArgumentParser(
130
+ prog="justapi",
131
+ description="JustAPI β€” Python web framework with a Rust core.",
132
+ )
133
+ parser.add_argument("--version", "-V", action="store_true", help="Show version")
134
+ sub = parser.add_subparsers(dest="command")
135
+
136
+ p_serve = sub.add_parser("serve", help="Run the app in the current directory")
137
+ p_serve.add_argument("addr", nargs="?", default="127.0.0.1:8000", help="Address to bind (default 127.0.0.1:8000)")
138
+
139
+ p_create = sub.add_parser("create", help="Scaffold a new project (requires Rust CLI)")
140
+ p_create.add_argument("name", help="Project name")
141
+ p_create.add_argument("rest", nargs=argparse.REMAINDER, help="Extra args passed to the Rust CLI")
142
+
143
+ p_check = sub.add_parser("check", help="Validate the app without starting the server")
144
+ p_check.add_argument("file", nargs="?", help="App file (default: auto-detect)")
145
+
146
+ p_openapi = sub.add_parser("openapi", help="Print the OpenAPI spec")
147
+ p_openapi.add_argument("file", nargs="?", help="App file (default: auto-detect)")
148
+
149
+ args = parser.parse_args(argv)
150
+
151
+ if args.version:
152
+ cmd_version(args)
153
+ return
154
+
155
+ if args.command == "serve":
156
+ cmd_serve(args)
157
+ elif args.command == "create":
158
+ cmd_create(args)
159
+ elif args.command == "check":
160
+ cmd_check(args)
161
+ elif args.command == "openapi":
162
+ cmd_openapi(args)
163
+ else:
164
+ # No command β†’ show help / quick start
165
+ if _delegate_or([]):
166
+ return
167
+ parser.print_help()
14
168
15
- print("JustAPI Runtime v2.0.0 (Python package)")
16
- print("Commands:")
17
- print(" justapi --version Display version")
18
- print(" python -m app.main Run application directly")
19
- print("\nFor high-performance Rust CLI features (justapi create, serve --reload, profile),")
20
- print("install justapi-cli via cargo: cargo install justapi-cli")
21
169
22
170
if __name__ == "__main__":
23
171
main()
24
-
25
172