apizit-linking 0.3.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.
@@ -0,0 +1,81 @@
1
+ """Declarative Python-to-HTTP binding engine for APIZIT."""
2
+
3
+ from apizit_linking.compiler import (
4
+ compile_linking_file,
5
+ compile_routes,
6
+ discover_linking_file,
7
+ load_linking_file,
8
+ resolve_linking_target,
9
+ validate_definition,
10
+ )
11
+ from apizit_linking.errors import (
12
+ FunctionLoadError,
13
+ InvalidParameterError,
14
+ InvocationBindingError,
15
+ LinkingConfigurationError,
16
+ LinkingError,
17
+ LinkingRequestError,
18
+ ParameterNotFoundError,
19
+ ParameterResolutionError,
20
+ )
21
+ from apizit_linking.models import (
22
+ AUTO_SOURCE_ORDER,
23
+ LINKING_FILENAMES,
24
+ SUPPORTED_SOURCES,
25
+ CompilationResult,
26
+ CompiledRoute,
27
+ Diagnostic,
28
+ LoadedDefinition,
29
+ ParameterDefinition,
30
+ ParameterSource,
31
+ RouteDefinition,
32
+ SignatureParameter,
33
+ route_from_runtime_dict,
34
+ )
35
+ from apizit_linking.runtime import (
36
+ RequestContext,
37
+ call_function,
38
+ convert_parameter,
39
+ invoke_function,
40
+ load_function,
41
+ load_functions,
42
+ resolve_arguments,
43
+ )
44
+
45
+ __all__ = [
46
+ "AUTO_SOURCE_ORDER",
47
+ "LINKING_FILENAMES",
48
+ "SUPPORTED_SOURCES",
49
+ "CompilationResult",
50
+ "CompiledRoute",
51
+ "Diagnostic",
52
+ "FunctionLoadError",
53
+ "InvalidParameterError",
54
+ "InvocationBindingError",
55
+ "LinkingConfigurationError",
56
+ "LinkingError",
57
+ "LinkingRequestError",
58
+ "LoadedDefinition",
59
+ "ParameterDefinition",
60
+ "ParameterNotFoundError",
61
+ "ParameterResolutionError",
62
+ "ParameterSource",
63
+ "RequestContext",
64
+ "RouteDefinition",
65
+ "SignatureParameter",
66
+ "compile_linking_file",
67
+ "compile_routes",
68
+ "call_function",
69
+ "convert_parameter",
70
+ "discover_linking_file",
71
+ "invoke_function",
72
+ "load_function",
73
+ "load_functions",
74
+ "load_linking_file",
75
+ "resolve_arguments",
76
+ "resolve_linking_target",
77
+ "route_from_runtime_dict",
78
+ "validate_definition",
79
+ ]
80
+
81
+ __version__ = "0.3.0"
apizit_linking/cli.py ADDED
@@ -0,0 +1,162 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import ipaddress
5
+ import json
6
+ import sys
7
+ from typing import Sequence
8
+
9
+ from apizit_linking.compiler import compile_linking_file, resolve_linking_target
10
+ from apizit_linking.errors import LinkingError
11
+ from apizit_linking.models import CompilationResult
12
+
13
+
14
+ def build_parser() -> argparse.ArgumentParser:
15
+ parser = argparse.ArgumentParser(
16
+ prog="apizit-linking",
17
+ description="Validate or preview an APIZIT declarative linking project.",
18
+ )
19
+ subparsers = parser.add_subparsers(dest="command", required=True)
20
+ validate = subparsers.add_parser("validate", help="Validate and compile a linking definition.")
21
+ validate.add_argument(
22
+ "target",
23
+ nargs="?",
24
+ default=".",
25
+ help="Project directory or apizit_linking YAML/JSON file.",
26
+ )
27
+ validate.add_argument(
28
+ "--project-root",
29
+ help="Project root used to resolve module:function references.",
30
+ )
31
+ validate.add_argument("--json", action="store_true", help="Emit a machine-readable result.")
32
+
33
+ preview = subparsers.add_parser(
34
+ "preview",
35
+ help="Run a local development server for a Linking project.",
36
+ )
37
+ preview.add_argument(
38
+ "target",
39
+ nargs="?",
40
+ default=".",
41
+ help="Project directory or apizit_linking YAML/JSON file.",
42
+ )
43
+ preview.add_argument(
44
+ "--project-root",
45
+ help="Project root used to resolve module:function references.",
46
+ )
47
+ preview.add_argument("--host", default="127.0.0.1", help="Interface to bind.")
48
+ preview.add_argument("--port", type=_valid_port, default=8000, help="TCP port to bind.")
49
+ preview.add_argument(
50
+ "--allow-network",
51
+ action="store_true",
52
+ help="Allow binding to a non-loopback interface.",
53
+ )
54
+ return parser
55
+
56
+
57
+ def run(argv: Sequence[str] | None = None) -> int:
58
+ args = build_parser().parse_args(argv)
59
+ linking_file, project_root = resolve_linking_target(args.target, args.project_root)
60
+ result = compile_linking_file(linking_file, project_root)
61
+ if args.command == "validate":
62
+ return _run_validate(result, as_json=args.json)
63
+ if args.command == "preview":
64
+ return _run_preview(
65
+ result, host=args.host, port=args.port, allow_network=args.allow_network
66
+ )
67
+ return 2
68
+
69
+
70
+ def _run_validate(result: CompilationResult, *, as_json: bool) -> int:
71
+ payload = {
72
+ "valid": result.is_valid,
73
+ "file": str(result.linking_file),
74
+ "project_root": str(result.project_root),
75
+ "version": result.version,
76
+ "routes": [route.definition.to_runtime_dict() for route in result.routes],
77
+ "diagnostics": [diagnostic.to_dict() for diagnostic in result.diagnostics],
78
+ }
79
+ if as_json:
80
+ print(json.dumps(payload, indent=2, ensure_ascii=False))
81
+ else:
82
+ state = "valid" if result.is_valid else "invalid"
83
+ print(f"APIZIT linking definition is {state}: {result.linking_file}")
84
+ print(f"Compiled routes: {len(result.routes)}")
85
+ for diagnostic in result.diagnostics:
86
+ print(diagnostic.format())
87
+ return 0 if result.is_valid else 1
88
+
89
+
90
+ def _run_preview(
91
+ result: CompilationResult,
92
+ *,
93
+ host: str,
94
+ port: int,
95
+ allow_network: bool,
96
+ ) -> int:
97
+ if not result.is_valid:
98
+ print(
99
+ f"Cannot start preview: invalid linking definition: {result.linking_file}",
100
+ file=sys.stderr,
101
+ )
102
+ for diagnostic in result.diagnostics:
103
+ print(diagnostic.format(), file=sys.stderr)
104
+ return 1
105
+ if not allow_network and not _is_loopback_host(host):
106
+ print(
107
+ f"Refusing to expose the preview server on {host!r}. "
108
+ "Pass --allow-network to bind outside the local machine.",
109
+ file=sys.stderr,
110
+ )
111
+ return 2
112
+
113
+ try:
114
+ from apizit_linking.preview import run_preview
115
+ except ModuleNotFoundError as error:
116
+ missing_root = (error.name or "").partition(".")[0]
117
+ if missing_root not in {"fastapi", "multipart", "starlette", "uvicorn"}:
118
+ raise
119
+ print(
120
+ 'Preview dependencies are not installed. Run: pip install "apizit-linking[preview]"',
121
+ file=sys.stderr,
122
+ )
123
+ return 2
124
+
125
+ for diagnostic in result.warnings:
126
+ print(diagnostic.format(), file=sys.stderr)
127
+ try:
128
+ run_preview(result, host=host, port=port)
129
+ except LinkingError as error:
130
+ print(f"Could not start preview: {error}", file=sys.stderr)
131
+ return 1
132
+ except KeyboardInterrupt:
133
+ return 0
134
+ return 0
135
+
136
+
137
+ def _valid_port(value: str) -> int:
138
+ try:
139
+ port = int(value)
140
+ except ValueError as error:
141
+ raise argparse.ArgumentTypeError("port must be an integer") from error
142
+ if not 1 <= port <= 65535:
143
+ raise argparse.ArgumentTypeError("port must be between 1 and 65535")
144
+ return port
145
+
146
+
147
+ def _is_loopback_host(host: str) -> bool:
148
+ normalized = host.strip().lower()
149
+ if normalized == "localhost":
150
+ return True
151
+ try:
152
+ return ipaddress.ip_address(normalized).is_loopback
153
+ except ValueError:
154
+ return False
155
+
156
+
157
+ def main() -> None:
158
+ raise SystemExit(run())
159
+
160
+
161
+ if __name__ == "__main__":
162
+ main()