agnara-http 0.1.0a8__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.
Files changed (37) hide show
  1. agnara_http/__init__.py +37 -0
  2. agnara_http/_asgi.py +71 -0
  3. agnara_http/_binding.py +780 -0
  4. agnara_http/_discovery.py +360 -0
  5. agnara_http/_dispatch.py +338 -0
  6. agnara_http/_documentation.py +550 -0
  7. agnara_http/_explorer.py +597 -0
  8. agnara_http/_exposures.py +85 -0
  9. agnara_http/_lifespan.py +134 -0
  10. agnara_http/_openapi.py +322 -0
  11. agnara_http/_problem.py +313 -0
  12. agnara_http/_publication.py +268 -0
  13. agnara_http/_redoc.py +188 -0
  14. agnara_http/_response.py +125 -0
  15. agnara_http/_routing.py +284 -0
  16. agnara_http/_scalar.py +186 -0
  17. agnara_http/_surfaces.py +259 -0
  18. agnara_http/_swagger.py +235 -0
  19. agnara_http/_vendor/redoc/2.5.3/LICENSE +22 -0
  20. agnara_http/_vendor/redoc/2.5.3/manifest.json +28 -0
  21. agnara_http/_vendor/redoc/2.5.3/redoc.standalone.js +1838 -0
  22. agnara_http/_vendor/redoc/2.5.3/redoc.standalone.js.LICENSE.txt +102 -0
  23. agnara_http/_vendor/scalar/1.67.0/LICENSE +21 -0
  24. agnara_http/_vendor/scalar/1.67.0/manifest.json +37 -0
  25. agnara_http/_vendor/scalar/1.67.0/standalone.js +2455 -0
  26. agnara_http/_vendor/swagger_ui/5.32.14/LICENSE +202 -0
  27. agnara_http/_vendor/swagger_ui/5.32.14/NOTICE +2 -0
  28. agnara_http/_vendor/swagger_ui/5.32.14/manifest.json +33 -0
  29. agnara_http/_vendor/swagger_ui/5.32.14/swagger-ui-bundle.js +2 -0
  30. agnara_http/_vendor/swagger_ui/5.32.14/swagger-ui-bundle.js.LICENSE.txt +104 -0
  31. agnara_http/_vendor/swagger_ui/5.32.14/swagger-ui.css +3 -0
  32. agnara_http/composition.py +960 -0
  33. agnara_http/py.typed +0 -0
  34. agnara_http-0.1.0a8.dist-info/METADATA +266 -0
  35. agnara_http-0.1.0a8.dist-info/RECORD +37 -0
  36. agnara_http-0.1.0a8.dist-info/WHEEL +4 -0
  37. agnara_http-0.1.0a8.dist-info/licenses/LICENSE +202 -0
@@ -0,0 +1,37 @@
1
+ """HTTP/ASGI exposure adapter for Agnara capabilities.
2
+
3
+ Owns the ASGI boundary, routing, request decoding, response encoding, HTTP
4
+ lifecycle, RFC 9457 failure mapping, OpenAPI generation, the authorized
5
+ introspection discovery endpoint and the read-only Agnara Explorer.
6
+
7
+ Depends on ``agnara-core``. Must not import a sibling adapter.
8
+ See ``ARCHITECTURE.md`` sections 3 and 4, and EPIC 6 in ``BACKLOG.md``.
9
+
10
+ The public surface is the composition API in ``agnara_http.composition`` and
11
+ nothing else. ``docs/HTTP_COMPOSITION.md`` is the supported usage guide and
12
+ records what ``0.1.0a4`` does not yet expose: the documentation UI providers,
13
+ the Agnara Explorer and the authorized discovery endpoint remain internal,
14
+ because the publication plan compiles placeholder routes and no product code
15
+ path renders a provider. Every other module here is underscore-prefixed and
16
+ carries no compatibility promise.
17
+ """
18
+
19
+ from .composition import (
20
+ Binding,
21
+ BindingSource,
22
+ Http,
23
+ HttpApplication,
24
+ HttpDefinitionError,
25
+ OpenApiInfo,
26
+ OpenApiOperation,
27
+ )
28
+
29
+ __all__ = [
30
+ "Binding",
31
+ "BindingSource",
32
+ "Http",
33
+ "HttpApplication",
34
+ "HttpDefinitionError",
35
+ "OpenApiInfo",
36
+ "OpenApiOperation",
37
+ ]
agnara_http/_asgi.py ADDED
@@ -0,0 +1,71 @@
1
+ """Dependency-free ASGI boundary for the HTTP adapter.
2
+
3
+ The module is internal until the HTTP exposure/composition API is reviewed.
4
+ It follows the ASGI 3 single-callable shape and recognizes HTTP scopes, plus
5
+ lifespan scopes when an application lifecycle is configured. Routing, request
6
+ binding, response mapping and WebSockets belong to other adapter modules.
7
+
8
+ A lifespan scope with no configured dispatcher is refused rather than silently
9
+ completed. Raising is how an ASGI application tells a server it has no
10
+ lifespan, and a server running `lifespan="auto"` handles that itself.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from collections.abc import Awaitable, Callable
16
+ from typing import Any
17
+
18
+ type _Scope = dict[str, Any]
19
+ type _Message = dict[str, Any]
20
+ type _Receive = Callable[[], Awaitable[_Message]]
21
+ type _Send = Callable[[_Message], Awaitable[None]]
22
+ type _HTTPDispatch = Callable[[_Scope, _Receive, _Send], Awaitable[None]]
23
+ type _LifespanDispatch = Callable[[_Scope, _Receive, _Send], Awaitable[None]]
24
+
25
+
26
+ class _UnsupportedScopeError(RuntimeError):
27
+ """Raised when an ASGI server offers a protocol this adapter does not support."""
28
+
29
+
30
+ class _ASGIBoundary:
31
+ """ASGI 3 callable that validates and delegates one connection scope."""
32
+
33
+ __slots__ = ("_dispatch", "_lifespan")
34
+
35
+ def __init__(
36
+ self,
37
+ dispatch: _HTTPDispatch,
38
+ lifespan: _LifespanDispatch | None = None,
39
+ ) -> None:
40
+ if not callable(dispatch):
41
+ raise TypeError(f"dispatch must be callable, got {type(dispatch).__name__}")
42
+ if lifespan is not None and not callable(lifespan):
43
+ raise TypeError(f"lifespan must be callable, got {type(lifespan).__name__}")
44
+ self._dispatch = dispatch
45
+ self._lifespan = lifespan
46
+
47
+ async def __call__(
48
+ self,
49
+ scope: _Scope,
50
+ receive: _Receive,
51
+ send: _Send,
52
+ ) -> None:
53
+ if not isinstance(scope, dict):
54
+ raise TypeError(f"ASGI scope must be a dictionary, got {type(scope).__name__}")
55
+
56
+ scope_type = scope.get("type")
57
+ if not isinstance(scope_type, str):
58
+ raise TypeError("ASGI scope 'type' must be a string")
59
+ if scope_type == "http":
60
+ handler = self._dispatch
61
+ elif scope_type == "lifespan" and self._lifespan is not None:
62
+ handler = self._lifespan
63
+ else:
64
+ raise _UnsupportedScopeError(f"unsupported ASGI scope type: {scope_type!r}")
65
+
66
+ if not callable(receive):
67
+ raise TypeError(f"ASGI receive must be callable, got {type(receive).__name__}")
68
+ if not callable(send):
69
+ raise TypeError(f"ASGI send must be callable, got {type(send).__name__}")
70
+
71
+ return await handler(scope, receive, send)