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.
- apizit_linking/__init__.py +81 -0
- apizit_linking/cli.py +162 -0
- apizit_linking/compiler.py +1090 -0
- apizit_linking/errors.py +88 -0
- apizit_linking/fastapi.py +221 -0
- apizit_linking/models.py +416 -0
- apizit_linking/preview.py +25 -0
- apizit_linking/runtime.py +645 -0
- apizit_linking/schema/apizit-linking-v1.schema.json +87 -0
- apizit_linking-0.3.0.dist-info/METADATA +256 -0
- apizit_linking-0.3.0.dist-info/RECORD +15 -0
- apizit_linking-0.3.0.dist-info/WHEEL +5 -0
- apizit_linking-0.3.0.dist-info/entry_points.txt +2 -0
- apizit_linking-0.3.0.dist-info/licenses/LICENSE +201 -0
- apizit_linking-0.3.0.dist-info/top_level.txt +1 -0
|
@@ -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()
|