tigrbl 0.3.7.dev2__py3-none-any.whl → 0.3.7.dev3__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 (48) hide show
  1. tigrbl/__init__.py +2 -2
  2. tigrbl/api/_router.py +1 -1
  3. tigrbl/api/api_spec.py +1 -1
  4. tigrbl/app/app_spec.py +1 -1
  5. tigrbl/bindings/__init__.py +1 -1
  6. tigrbl/bindings/rest/common.py +2 -2
  7. tigrbl/bindings/rest/fastapi.py +4 -4
  8. tigrbl/bindings/rest/helpers.py +1 -1
  9. tigrbl/bindings/rest/io.py +1 -1
  10. tigrbl/bindings/rest/routing.py +1 -1
  11. tigrbl/core/resolver.py +1 -1
  12. tigrbl/core/router_runtime.py +2 -2
  13. tigrbl/headers/__init__.py +2 -2
  14. tigrbl/headers/_header.py +25 -5
  15. tigrbl/op/types.py +1 -1
  16. tigrbl/requests/__init__.py +4 -2
  17. tigrbl/requests/_request.py +48 -4
  18. tigrbl/responses/__init__.py +37 -4
  19. tigrbl/responses/_response.py +26 -4
  20. tigrbl/{response → responses}/shortcuts.py +1 -1
  21. tigrbl/runtime/atoms/response/headers_from_payload.py +1 -1
  22. tigrbl/runtime/atoms/response/renderer.py +2 -2
  23. tigrbl/runtime/atoms/response/templates.py +1 -1
  24. tigrbl/runtime/executor/types.py +1 -1
  25. tigrbl/system/diagnostics/compat.py +1 -1
  26. tigrbl/system/docs/lens.py +1 -1
  27. tigrbl/system/docs/openapi/mount.py +1 -1
  28. tigrbl/system/docs/openrpc.py +1 -1
  29. tigrbl/system/docs/swagger.py +1 -1
  30. tigrbl/system/favicon/__init__.py +1 -1
  31. tigrbl/table/_base.py +1 -1
  32. tigrbl/table/table_spec.py +1 -1
  33. tigrbl/transport/asgi_wsgi.py +48 -44
  34. tigrbl/transport/jsonrpc/dispatcher.py +36 -10
  35. tigrbl/types/__init__.py +2 -2
  36. tigrbl/types/authn_abc.py +1 -1
  37. {tigrbl-0.3.7.dev2.dist-info → tigrbl-0.3.7.dev3.dist-info}/METADATA +1 -1
  38. {tigrbl-0.3.7.dev2.dist-info → tigrbl-0.3.7.dev3.dist-info}/RECORD +45 -48
  39. tigrbl/response/__init__.py +0 -54
  40. tigrbl/response/stdapi.py +0 -26
  41. tigrbl/transport/request.py +0 -9
  42. /tigrbl/{response → responses}/README.md +0 -0
  43. /tigrbl/{response → responses}/bind.py +0 -0
  44. /tigrbl/{response → responses}/decorators.py +0 -0
  45. /tigrbl/{response → responses}/resolver.py +0 -0
  46. /tigrbl/{response → responses}/types.py +0 -0
  47. {tigrbl-0.3.7.dev2.dist-info → tigrbl-0.3.7.dev3.dist-info}/WHEEL +0 -0
  48. {tigrbl-0.3.7.dev2.dist-info → tigrbl-0.3.7.dev3.dist-info}/licenses/LICENSE +0 -0
tigrbl/__init__.py CHANGED
@@ -43,8 +43,8 @@ from .op import alias_ctx, op_ctx, alias, op_alias
43
43
  from .hook import hook_ctx
44
44
  from .engine.decorators import engine_ctx
45
45
  from .schema.decorators import schema_ctx
46
- from .response.decorators import response_ctx
47
- from .response.types import ResponseSpec
46
+ from .responses.decorators import response_ctx
47
+ from .responses.types import ResponseSpec
48
48
 
49
49
  # ── Bindings (model + API orchestration) ───────────────────────────────────────
50
50
  from .bindings import (
tigrbl/api/_router.py CHANGED
@@ -37,7 +37,7 @@ from tigrbl.transport.httpx import ensure_httpx_sync_transport
37
37
  from ._route import Route
38
38
  from ..system.docs.openapi import build_openapi, mount_openapi
39
39
  from ..system.docs.swagger import mount_swagger
40
- from ..requests._request import Request
40
+ from ..requests import Request
41
41
  from ..transport.rest.decorators import (
42
42
  delete as rest_delete,
43
43
  get as rest_get,
tigrbl/api/api_spec.py CHANGED
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  from dataclasses import dataclass, field
4
4
  from typing import Any, Callable, Optional, Sequence
5
5
  from ..engine.engine_spec import EngineCfg
6
- from ..response.types import ResponseSpec
6
+ from ..responses.types import ResponseSpec
7
7
 
8
8
 
9
9
  @dataclass
tigrbl/app/app_spec.py CHANGED
@@ -4,7 +4,7 @@ from dataclasses import dataclass, field
4
4
  from typing import Any, Callable, Optional, Sequence
5
5
 
6
6
  from ..engine.engine_spec import EngineCfg
7
- from ..response.types import ResponseSpec
7
+ from ..responses.types import ResponseSpec
8
8
 
9
9
 
10
10
  @dataclass(eq=False)
@@ -46,7 +46,7 @@ from .hooks import normalize_and_attach as build_hooks
46
46
  from .handlers import build_and_attach as build_handlers
47
47
  from .rpc import register_and_attach as register_rpc
48
48
  from .rest import build_router_and_attach as build_rest
49
- from ..response.bind import bind as bind_response
49
+ from ..responses.bind import bind as bind_response
50
50
 
51
51
  # API facade integration
52
52
  from .api import include_model, include_models, rpc_call
@@ -13,11 +13,11 @@ from pydantic import BaseModel
13
13
 
14
14
  from ...api._api import APIRouter as Router
15
15
  from ...core.crud.params import Body, Path, Query
16
- from ...responses._response import Response
16
+ from ...responses import Response
17
17
  from ...runtime.status.exceptions import HTTPException
18
18
  from ...runtime.status.mappings import status as _status
19
19
  from ...security.dependencies import Depends, Security
20
- from ...requests._request import Request
20
+ from ...requests import Request
21
21
  from .helpers import (
22
22
  _Key,
23
23
  _coerce_parent_kw,
@@ -1,9 +1,9 @@
1
1
  """Deprecated compatibility module for REST ASGI-style primitives.
2
2
 
3
3
  Prefer importing directly from:
4
- - ``tigrbl.response`` for response classes
4
+ - ``tigrbl.responses`` for response classes
5
5
  - ``tigrbl.runtime.status`` for ``status`` and ``HTTPException``
6
- - ``tigrbl.api._api`` / ``tigrbl.requests._request`` / ``tigrbl.security.dependencies``
6
+ - ``tigrbl.api._api`` / ``tigrbl.requests`` / ``tigrbl.security.dependencies``
7
7
  """
8
8
 
9
9
  from __future__ import annotations
@@ -21,11 +21,11 @@ from ...response import (
21
21
  )
22
22
  from ...runtime.status import HTTPException, status
23
23
  from ...security.dependencies import Depends, Security
24
- from ...requests._request import Request
24
+ from ...requests import Request
25
25
 
26
26
  warnings.warn(
27
27
  "tigrbl.bindings.rest.asgi is deprecated; import from "
28
- "tigrbl.response, tigrbl.runtime.status, and concrete modules instead.",
28
+ "tigrbl.responses, tigrbl.runtime.status, and concrete modules instead.",
29
29
  DeprecationWarning,
30
30
  stacklevel=2,
31
31
  )
@@ -4,7 +4,7 @@ import logging
4
4
  from types import SimpleNamespace
5
5
  from typing import Any, Awaitable, Callable, Dict, Mapping, Sequence, Tuple
6
6
 
7
- from ...requests._request import Request
7
+ from ...requests import Request
8
8
  from ...op.types import PHASES
9
9
 
10
10
  try:
@@ -12,7 +12,7 @@ from pydantic import BaseModel, Field, create_model
12
12
  from ...core.crud.params import Query
13
13
  from ...runtime.status.exceptions import HTTPException
14
14
  from ...runtime.status.mappings import status as _status
15
- from ...requests._request import Request
15
+ from ...requests import Request
16
16
  from .helpers import _ensure_jsonable
17
17
  from ...op import OpSpec
18
18
 
@@ -9,7 +9,7 @@ from typing import Any, Dict, Optional, Sequence, Tuple
9
9
  from ...runtime.status.exceptions import HTTPException
10
10
  from ...runtime.status.mappings import status as _status
11
11
  from ...security.dependencies import Depends, Security
12
- from ...requests._request import Request
12
+ from ...requests import Request
13
13
  from ...op import OpSpec
14
14
  from ...security import HTTPBearer
15
15
  from ...op.types import CANON
tigrbl/core/resolver.py CHANGED
@@ -7,7 +7,7 @@ from typing import Annotated, Any, Iterable, get_args, get_origin
7
7
  from ..core.crud.params import Param
8
8
  from ..runtime.status.exceptions import HTTPException
9
9
  from ..runtime.status.mappings import status
10
- from ..requests._request import Request
10
+ from ..requests import Request
11
11
 
12
12
 
13
13
  def split_annotated(annotation: Any) -> tuple[Any, tuple[Any, ...]]:
@@ -10,10 +10,10 @@ from ..api.resolve import (
10
10
  resolve_handler_kwargs,
11
11
  resolve_route_dependencies,
12
12
  )
13
- from ..responses._response import Response
13
+ from ..responses import Response
14
14
  from ..runtime.status.exceptions import HTTPException
15
15
  from ..runtime.status.mappings import status
16
- from ..requests._request import Request
16
+ from ..requests import Request
17
17
 
18
18
 
19
19
  def _is_http_response_like(obj: Any) -> bool:
@@ -1,3 +1,3 @@
1
- from ._header import Headers
1
+ from ._header import HeaderCookies, Headers
2
2
 
3
- __all__ = ["Headers"]
3
+ __all__ = ["Headers", "HeaderCookies"]
tigrbl/headers/_header.py CHANGED
@@ -3,6 +3,17 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from collections.abc import Iterable, Mapping, MutableMapping
6
+ from http.cookies import SimpleCookie
7
+
8
+
9
+ class HeaderCookies(dict[str, str]):
10
+ """Dot-addressable cookie mapping parsed from a Cookie header value."""
11
+
12
+ def __getattr__(self, name: str) -> str:
13
+ try:
14
+ return self[name]
15
+ except KeyError as exc: # pragma: no cover - defensive
16
+ raise AttributeError(name) from exc
6
17
 
7
18
 
8
19
  class Headers(MutableMapping[str, str]):
@@ -24,6 +35,12 @@ class Headers(MutableMapping[str, str]):
24
35
  def _attribute_key(name: str) -> str:
25
36
  return name.replace("_", "-").lower()
26
37
 
38
+ @staticmethod
39
+ def _parse_cookie_header(value: str) -> HeaderCookies:
40
+ parsed = SimpleCookie()
41
+ parsed.load(value)
42
+ return HeaderCookies({name: morsel.value for name, morsel in parsed.items()})
43
+
27
44
  def __getitem__(self, key: str) -> str:
28
45
  return self._data[key.lower()][1]
29
46
 
@@ -53,11 +70,14 @@ class Headers(MutableMapping[str, str]):
53
70
  return default
54
71
  return item[1]
55
72
 
56
- def __getattr__(self, name: str) -> str:
73
+ def __getattr__(self, name: str) -> str | HeaderCookies:
57
74
  key = self._attribute_key(name)
58
- if key in self._data:
59
- return self._data[key][1]
60
- raise AttributeError(name)
75
+ if key not in self._data:
76
+ raise AttributeError(name)
77
+ value = self._data[key][1]
78
+ if key == "cookie":
79
+ return self._parse_cookie_header(value)
80
+ return value
61
81
 
62
82
  def __setattr__(self, name: str, value: str) -> None:
63
83
  if name.startswith("_"):
@@ -66,4 +86,4 @@ class Headers(MutableMapping[str, str]):
66
86
  self[self._attribute_key(name)] = value
67
87
 
68
88
 
69
- __all__ = ["Headers"]
89
+ __all__ = ["Headers", "HeaderCookies"]
tigrbl/op/types.py CHANGED
@@ -8,7 +8,7 @@ from typing import Any, Literal, Mapping, Optional, Tuple, cast
8
8
  from ..config.constants import CANON as CANONICAL_VERB_TUPLE
9
9
  from ..hook.types import PHASE, HookPhase, PHASES, Ctx, StepFn, HookPredicate
10
10
  from ..hook import HookSpec as OpHook
11
- from ..response.types import ResponseSpec
11
+ from ..responses.types import ResponseSpec
12
12
  from ..engine.engine_spec import EngineCfg
13
13
  from typing import TYPE_CHECKING
14
14
 
@@ -1,3 +1,5 @@
1
- from ._request import Request, AwaitableValue, URL
1
+ from tigrbl.headers import Headers
2
2
 
3
- __all__ = ["Request", "AwaitableValue", "URL"]
3
+ from ._request import AwaitableValue, Request, URL
4
+
5
+ __all__ = ["Headers", "Request", "AwaitableValue", "URL"]
@@ -2,13 +2,14 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import base64
5
6
  import json as json_module
6
7
  from dataclasses import dataclass, field
7
8
  from http.cookies import SimpleCookie
8
9
  from types import SimpleNamespace
9
10
  from typing import Any
10
11
 
11
- from tigrbl.headers import Headers
12
+ from tigrbl.headers import HeaderCookies, Headers
12
13
 
13
14
 
14
15
  @dataclass(frozen=True)
@@ -64,6 +65,15 @@ class AwaitableValue:
64
65
  return len(self.value)
65
66
 
66
67
 
68
+ def _b64url_encode(data: bytes) -> str:
69
+ return base64.urlsafe_b64encode(data).decode("ascii").rstrip("=")
70
+
71
+
72
+ def _b64url_decode(data: str) -> bytes:
73
+ pad = "=" * (-len(data) % 4)
74
+ return base64.urlsafe_b64decode((data + pad).encode("ascii"))
75
+
76
+
67
77
  @dataclass
68
78
  class Request:
69
79
  method: str
@@ -111,11 +121,45 @@ class Request:
111
121
  return {name: vals[0] for name, vals in self.query.items() if vals}
112
122
 
113
123
  @property
114
- def cookies(self) -> dict[str, str]:
124
+ def cookies(self) -> HeaderCookies:
115
125
  raw = self.headers.get("cookie", "") or ""
116
126
  parsed = SimpleCookie()
117
127
  parsed.load(raw)
118
- return {name: morsel.value for name, morsel in parsed.items()}
128
+ return HeaderCookies({name: morsel.value for name, morsel in parsed.items()})
119
129
 
130
+ @property
131
+ def bearer_token(self) -> str | None:
132
+ authorization = self.headers.get("authorization", "") or ""
133
+ scheme, _, token = authorization.partition(" ")
134
+ if scheme.lower() == "bearer" and token:
135
+ return token
136
+ return None
137
+
138
+ @property
139
+ def session_token(self) -> str | None:
140
+ bearer = self.bearer_token
141
+ if bearer:
142
+ return bearer
143
+ return self.cookies.get("sid")
120
144
 
121
- __all__ = ["Request", "AwaitableValue", "URL"]
145
+ @property
146
+ def client(self) -> SimpleNamespace:
147
+ host = ""
148
+ try:
149
+ client = self.scope.get("client")
150
+ except AttributeError:
151
+ client = None
152
+ if isinstance(client, tuple) and client:
153
+ host = str(client[0])
154
+ return SimpleNamespace(ip=host, host=host)
155
+
156
+ @staticmethod
157
+ def b64url_encode(data: bytes) -> str:
158
+ return _b64url_encode(data)
159
+
160
+ @staticmethod
161
+ def b64url_decode(data: str) -> bytes:
162
+ return _b64url_decode(data)
163
+
164
+
165
+ __all__ = ["Request", "AwaitableValue", "URL", "_b64url_encode", "_b64url_decode"]
@@ -1,14 +1,28 @@
1
+ from tigrbl.headers import Headers
2
+
1
3
  from ._response import (
2
- Response,
3
- JSONResponse,
4
+ FileResponse,
4
5
  HTMLResponse,
6
+ JSONResponse,
5
7
  PlainTextResponse,
6
- StreamingResponse,
7
- FileResponse,
8
8
  RedirectResponse,
9
+ Response,
10
+ StreamingResponse,
11
+ )
12
+ from .decorators import (
13
+ get_attached_response_alias,
14
+ get_attached_response_spec,
15
+ response_ctx,
9
16
  )
17
+ from .resolver import infer_hints, resolve_response_spec
18
+ from .shortcuts import as_file, as_html, as_json, as_redirect, as_stream, as_text
19
+ from .types import Response as ResponseConfig
20
+ from .types import ResponseKind, ResponseSpec, Template, TemplateSpec
21
+ from ..runtime.atoms.response.renderer import ResponseHints, render
22
+ from ..runtime.atoms.response.templates import render_template
10
23
 
11
24
  __all__ = [
25
+ "Headers",
12
26
  "Response",
13
27
  "JSONResponse",
14
28
  "HTMLResponse",
@@ -16,4 +30,23 @@ __all__ = [
16
30
  "StreamingResponse",
17
31
  "FileResponse",
18
32
  "RedirectResponse",
33
+ "response_ctx",
34
+ "get_attached_response_spec",
35
+ "get_attached_response_alias",
36
+ "ResponseSpec",
37
+ "ResponseKind",
38
+ "TemplateSpec",
39
+ "ResponseConfig",
40
+ "Template",
41
+ "resolve_response_spec",
42
+ "infer_hints",
43
+ "as_json",
44
+ "as_html",
45
+ "as_text",
46
+ "as_redirect",
47
+ "as_stream",
48
+ "as_file",
49
+ "ResponseHints",
50
+ "render",
51
+ "render_template",
19
52
  ]
@@ -9,7 +9,26 @@ from http.cookies import SimpleCookie
9
9
  from pathlib import Path
10
10
  from typing import Any, AsyncIterator, Iterable, Mapping
11
11
 
12
- from tigrbl.headers import Headers
12
+ from tigrbl.headers import HeaderCookies, Headers
13
+
14
+
15
+ class _JSONDualMethod:
16
+ def __get__(self, obj: "Response" | None, owner: type["Response"]):
17
+ if obj is None:
18
+
19
+ def _factory(
20
+ data: Any,
21
+ status_code: int = 200,
22
+ headers: Mapping[str, str] | None = None,
23
+ ) -> "Response":
24
+ return owner.from_json(data, status_code=status_code, headers=headers)
25
+
26
+ return _factory
27
+
28
+ def _instance_json() -> Any:
29
+ return obj.json_body()
30
+
31
+ return _instance_json
13
32
 
14
33
 
15
34
  @dataclass
@@ -20,6 +39,8 @@ class Response:
20
39
  media_type: str | None = None
21
40
  _headers: Headers = field(init=False, repr=False)
22
41
 
42
+ json = _JSONDualMethod()
43
+
23
44
  def __post_init__(self) -> None:
24
45
  self._headers = Headers(self.headers)
25
46
 
@@ -28,6 +49,7 @@ class Response:
28
49
  return {
29
50
  200: "OK",
30
51
  201: "Created",
52
+ 205: "Reset Content",
31
53
  204: "No Content",
32
54
  301: "Moved Permanently",
33
55
  302: "Found",
@@ -65,12 +87,12 @@ class Response:
65
87
  return json_module.loads(self.body.decode("utf-8"))
66
88
 
67
89
  @property
68
- def cookies(self) -> dict[str, str]:
90
+ def cookies(self) -> HeaderCookies:
69
91
  cookie = SimpleCookie()
70
92
  for name, value in self._headers.items():
71
93
  if name == "set-cookie":
72
94
  cookie.load(value)
73
- return {name: morsel.value for name, morsel in cookie.items()}
95
+ return HeaderCookies({name: morsel.value for name, morsel in cookie.items()})
74
96
 
75
97
  def set_cookie(self, key: str, value: str, *, path: str = "/") -> None:
76
98
  cookie = SimpleCookie()
@@ -80,7 +102,7 @@ class Response:
80
102
  self.headers = self._headers.as_list()
81
103
 
82
104
  @classmethod
83
- def json(
105
+ def from_json(
84
106
  cls,
85
107
  data: Any,
86
108
  status_code: int = 200,
@@ -8,7 +8,7 @@ from datetime import datetime, timezone
8
8
  from pathlib import Path
9
9
  from typing import Any, AsyncIterable, Iterable, Mapping, Optional, Union
10
10
 
11
- from .stdapi import (
11
+ from ._response import (
12
12
  FileResponse,
13
13
  HTMLResponse,
14
14
  JSONResponse,
@@ -13,7 +13,7 @@ def run(_, ctx) -> None:
13
13
  - Honors op-specific exposure via ``io.out_verbs``.
14
14
  Complexity: O(#fields in opview).
15
15
  """
16
- from tigrbl.response import ResponseHints
16
+ from tigrbl.responses import ResponseHints
17
17
 
18
18
  resp = getattr(ctx, "response", None)
19
19
  if resp is None:
@@ -4,10 +4,10 @@ from pathlib import Path
4
4
  from typing import Any, AsyncIterable, Iterable, Mapping, Optional, Union, cast
5
5
  import logging
6
6
 
7
- from ....responses._response import Response
7
+ from ....responses import Response
8
8
  from ....transport.background import BackgroundTask
9
9
 
10
- from ....response.shortcuts import (
10
+ from ....responses.shortcuts import (
11
11
  as_file,
12
12
  as_html,
13
13
  as_json,
@@ -3,7 +3,7 @@ from functools import lru_cache
3
3
  import logging
4
4
  from typing import Any, Dict, Iterable, Optional, Tuple
5
5
 
6
- from ....requests._request import Request
6
+ from ....requests import Request
7
7
  from ....deps.jinja import (
8
8
  Environment,
9
9
  FileSystemLoader,
@@ -14,7 +14,7 @@ from typing import (
14
14
  runtime_checkable,
15
15
  )
16
16
 
17
- from ...requests._request import Request
17
+ from ...requests import Request
18
18
  from sqlalchemy.ext.asyncio import AsyncSession
19
19
  from sqlalchemy.orm import Session
20
20
 
@@ -4,7 +4,7 @@ from typing import Any, Callable, Sequence
4
4
  from types import SimpleNamespace
5
5
 
6
6
  try:
7
- from ...responses._response import JSONResponse
7
+ from ...responses import JSONResponse
8
8
  from ...types import Depends, Request, Router
9
9
  except Exception: # pragma: no cover
10
10
 
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  from typing import Any
4
4
  from urllib.parse import quote
5
5
 
6
- from ...responses._response import Response
6
+ from ...responses import Response
7
7
 
8
8
 
9
9
  TIGRBL_LENS_VERSION = "0.0.14"
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from typing import Any
4
4
 
5
- from ....responses._response import Response
5
+ from ....responses import Response
6
6
  from .schema import openapi
7
7
 
8
8
 
@@ -4,7 +4,7 @@ from typing import Any, Dict, Iterable, List, Mapping, Sequence
4
4
 
5
5
  from pydantic import BaseModel
6
6
 
7
- from ...responses._response import Response
7
+ from ...responses import Response
8
8
  from ...op import OpSpec
9
9
 
10
10
  JsonObject = Dict[str, Any]
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from typing import Any
4
4
 
5
- from ...responses._response import Response
5
+ from ...responses import Response
6
6
 
7
7
 
8
8
  def build_swagger_html(router: Any, request: Any) -> str:
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  from pathlib import Path
4
4
  from typing import Any
5
5
 
6
- from ...responses._response import FileResponse
6
+ from ...responses import FileResponse
7
7
 
8
8
  FAVICON_PATH = Path(__file__).with_name("assets") / "favicon.svg"
9
9
 
tigrbl/table/_base.py CHANGED
@@ -379,7 +379,7 @@ class Base(DeclarativeBase):
379
379
 
380
380
  # 2.6) Collect response specs declared via @response_ctx
381
381
  try:
382
- from tigrbl.response import (
382
+ from tigrbl.responses import (
383
383
  get_attached_response_spec,
384
384
  get_attached_response_alias,
385
385
  )
@@ -4,7 +4,7 @@ from dataclasses import dataclass, field
4
4
  from typing import Any, Callable, Optional, Sequence
5
5
 
6
6
  from ..engine.engine_spec import EngineCfg
7
- from ..response.types import ResponseSpec
7
+ from ..responses.types import ResponseSpec
8
8
 
9
9
 
10
10
  @dataclass
@@ -8,47 +8,32 @@ from typing import Any, Callable
8
8
  from urllib.parse import parse_qs
9
9
 
10
10
  from tigrbl.middlewares import apply_middlewares
11
- from tigrbl.requests._request import Request
12
- from tigrbl.responses._response import Response
11
+ from tigrbl.requests import Request
12
+ from tigrbl.responses import Response
13
13
 
14
14
 
15
- _BODYLESS_STATUS_CODES = {204, 304}
15
+ NO_BODY_STATUS = set(range(100, 200)) | {204, 205, 304}
16
16
 
17
17
 
18
- def _normalize_http_response(resp: Response, method: str) -> Response:
19
- """Normalize payload/body headers before writing to ASGI/WSGI transports."""
18
+ def _finalize_response(
19
+ scope: dict[str, Any],
20
+ status: int,
21
+ headers: list[tuple[bytes, bytes]],
22
+ body: bytes,
23
+ ) -> tuple[list[tuple[bytes, bytes]], bytes]:
24
+ """Enforce HTTP body/header invariants immediately before transport writes."""
20
25
 
21
- method_upper = method.upper()
22
- is_bodyless_status = (
23
- 100 <= resp.status_code < 200 or resp.status_code in _BODYLESS_STATUS_CODES
24
- )
25
- should_strip_body = is_bodyless_status or method_upper == "HEAD"
26
-
27
- headers = [(k, v) for k, v in resp.headers]
28
-
29
- if should_strip_body:
30
- resp.body = b""
31
- headers = [
32
- (k, v)
33
- for k, v in headers
34
- if k.lower() not in {"content-length", "transfer-encoding"}
35
- ]
36
- else:
37
- body_length = str(len(resp.body))
38
- has_content_length = False
39
- normalized_headers: list[tuple[str, str]] = []
40
- for key, value in headers:
41
- if key.lower() == "content-length":
42
- normalized_headers.append((key, body_length))
43
- has_content_length = True
44
- else:
45
- normalized_headers.append((key, value))
46
- if not has_content_length:
47
- normalized_headers.append(("content-length", body_length))
48
- headers = normalized_headers
49
-
50
- resp.headers = headers
51
- return resp
26
+ method = str(scope.get("method", "GET")).upper()
27
+
28
+ if method == "HEAD" or status in NO_BODY_STATUS:
29
+ drop = {b"content-length", b"content-type", b"transfer-encoding"}
30
+ headers = [(k, v) for (k, v) in headers if k.lower() not in drop]
31
+ return headers, b""
32
+
33
+ headers = [(k, v) for (k, v) in headers if k.lower() != b"content-length"]
34
+ headers.append((b"content-length", str(len(body)).encode("latin-1")))
35
+
36
+ return headers, body
52
37
 
53
38
 
54
39
  async def asgi_app(
@@ -121,17 +106,27 @@ async def asgi_app(
121
106
  more_body = message.get("more_body", False)
122
107
 
123
108
  req = request_from_asgi(router, scope, body)
124
- resp = _normalize_http_response(await router._dispatch(req), req.method)
109
+ resp = await router._dispatch(req)
110
+ headers, finalized_body = _finalize_response(
111
+ scope,
112
+ resp.status_code,
113
+ [(k.encode("latin-1"), v.encode("latin-1")) for k, v in resp.headers],
114
+ resp.body,
115
+ )
125
116
  await send(
126
117
  {
127
118
  "type": "http.response.start",
128
119
  "status": resp.status_code,
129
- "headers": [
130
- (k.encode("latin-1"), v.encode("latin-1")) for k, v in resp.headers
131
- ],
120
+ "headers": headers,
121
+ }
122
+ )
123
+ await send(
124
+ {
125
+ "type": "http.response.body",
126
+ "body": finalized_body,
127
+ "more_body": False,
132
128
  }
133
129
  )
134
- await send({"type": "http.response.body", "body": resp.body})
135
130
 
136
131
  app = apply_middlewares(_endpoint, list(getattr(router, "_middlewares", [])))
137
132
  await app(scope, receive, send)
@@ -146,9 +141,18 @@ def wsgi_app(
146
141
  _environ: dict[str, Any], _start_response: Callable[..., Any]
147
142
  ) -> list[bytes]:
148
143
  req = request_from_wsgi(router, _environ)
149
- resp = _normalize_http_response(asyncio.run(router._dispatch(req)), req.method)
150
- _start_response(resp.status_line(), resp.headers)
151
- return [resp.body]
144
+ resp = asyncio.run(router._dispatch(req))
145
+ headers, finalized_body = _finalize_response(
146
+ {"method": req.method},
147
+ resp.status_code,
148
+ [(k.encode("latin-1"), v.encode("latin-1")) for k, v in resp.headers],
149
+ resp.body,
150
+ )
151
+ _start_response(
152
+ resp.status_line(),
153
+ [(k.decode("latin-1"), v.decode("latin-1")) for k, v in headers],
154
+ )
155
+ return [finalized_body]
152
156
 
153
157
  app = apply_middlewares(_endpoint, list(getattr(router, "_middlewares", [])))
154
158
 
@@ -75,7 +75,7 @@ except Exception: # pragma: no cover
75
75
  self.detail = detail
76
76
 
77
77
 
78
- from ...runtime.status import ERROR_MESSAGES, http_exc_to_rpc
78
+ from ...runtime.status import ERROR_MESSAGES, _RPC_TO_HTTP, http_exc_to_rpc
79
79
  from ...config.constants import TIGRBL_AUTH_CONTEXT_ATTR
80
80
  from .models import RPCRequest, RPCResponse
81
81
  from ...system.docs import mount_openrpc
@@ -96,6 +96,26 @@ Json = Mapping[str, Any]
96
96
  Batch = Sequence[Mapping[str, Any]]
97
97
 
98
98
 
99
+ def _log_rpc_success(method: Any, rid: Any) -> None:
100
+ logger.info(
101
+ "jsonrpc response method=%s id=%s status_code=%s",
102
+ method,
103
+ rid,
104
+ 200,
105
+ )
106
+
107
+
108
+ def _log_rpc_error(method: Any, rid: Any, code: int, message: str) -> None:
109
+ logger.info(
110
+ "jsonrpc response method=%s id=%s status_code=%s error_code=%s error_message=%s",
111
+ method,
112
+ rid,
113
+ _RPC_TO_HTTP.get(code, 500),
114
+ code,
115
+ message,
116
+ )
117
+
118
+
99
119
  def _request_obj_to_mapping(obj: RPCRequest | Mapping[str, Any]) -> Mapping[str, Any]:
100
120
  """Convert endpoint payload objects to the mapping expected by dispatcher."""
101
121
  if isinstance(obj, RPCRequest):
@@ -117,34 +137,40 @@ async def _dispatch_one(
117
137
  or None if it's a "notification" (no id field).
118
138
  """
119
139
  rid = obj.get("id", 1)
140
+ method = obj.get("method")
141
+
142
+ def _rpc_error(code: int, message: str, data: Any | None = None) -> Dict[str, Any]:
143
+ _log_rpc_error(method, rid, code, message)
144
+ return _err(code, message, rid, data)
145
+
120
146
  try:
121
147
  # Basic JSON-RPC validation
122
148
  if not isinstance(obj, Mapping):
123
- return _err(-32600, "Invalid Request", rid) # not an object
149
+ return _rpc_error(-32600, "Invalid Request") # not an object
124
150
  # Be lenient: default to 2.0 when "jsonrpc" is omitted
125
151
  if obj.get("jsonrpc", "2.0") != "2.0":
126
- return _err(-32600, "Invalid Request", rid)
152
+ return _rpc_error(-32600, "Invalid Request")
127
153
  method = obj.get("method")
128
154
  if not isinstance(method, str) or "." not in method:
129
- return _err(-32601, "Method not found", rid)
155
+ return _rpc_error(-32601, "Method not found")
130
156
 
131
157
  model_name, alias = method.split(".", 1)
132
158
  model = _model_for(api, model_name)
133
159
  if model is None:
134
- return _err(-32601, f"Unknown model '{model_name}'", rid)
160
+ return _rpc_error(-32601, f"Unknown model '{model_name}'")
135
161
 
136
162
  # Locate RPC callable built by bindings.rpc
137
163
  rpc_ns = getattr(model, "rpc", None)
138
164
  rpc_call = getattr(rpc_ns, alias, None)
139
165
  if rpc_call is None:
140
- return _err(-32601, f"Method not found: {model_name}.{alias}", rid)
166
+ return _rpc_error(-32601, f"Method not found: {model_name}.{alias}")
141
167
 
142
168
  # Params
143
169
  try:
144
170
  params = _normalize_params(obj.get("params"))
145
171
  except HTTPException as exc:
146
172
  code, msg, data = http_exc_to_rpc(exc)
147
- return _err(code, msg, rid, data)
173
+ return _rpc_error(code, msg, data)
148
174
 
149
175
  # Enforce auth when required
150
176
  if getattr(api, "_authn", None):
@@ -169,16 +195,16 @@ async def _dispatch_one(
169
195
 
170
196
  # Execute
171
197
  result = await rpc_call(params, db=db, request=request, ctx=base_ctx)
172
-
198
+ _log_rpc_success(method, rid)
173
199
  return _ok(result, rid)
174
200
 
175
201
  except HTTPException as exc:
176
202
  code, msg, data = http_exc_to_rpc(exc)
177
- return _err(code, msg, rid, data)
203
+ return _rpc_error(code, msg, data)
178
204
  except Exception:
179
205
  logger.exception("jsonrpc dispatch failed")
180
206
  # Internal error (per JSON-RPC); do not leak details
181
- return _err(-32603, ERROR_MESSAGES.get(-32603, "Internal error"), rid)
207
+ return _rpc_error(-32603, ERROR_MESSAGES.get(-32603, "Internal error"))
182
208
 
183
209
 
184
210
  # --------------------------------------------------------------------------- #
tigrbl/types/__init__.py CHANGED
@@ -54,10 +54,10 @@ from ..deps.pydantic import (
54
54
 
55
55
  from ..api._api import APIRouter, Router
56
56
  from ..core.crud.params import Body, Path
57
- from ..responses._response import Response
57
+ from ..responses import Response
58
58
  from ..runtime.status.exceptions import HTTPException, StatusDetailError
59
59
  from ..security.dependencies import Depends, Security
60
- from ..requests._request import Request
60
+ from ..requests import Request
61
61
 
62
62
  # ── Local Package ─────────────────────────────────────────────────────────
63
63
  from .op import _Op, _SchemaVerb
tigrbl/types/authn_abc.py CHANGED
@@ -2,7 +2,7 @@
2
2
  from __future__ import annotations
3
3
  from abc import ABC, abstractmethod
4
4
 
5
- from ..requests._request import Request
5
+ from ..requests import Request
6
6
 
7
7
 
8
8
  class AuthNProvider(ABC):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tigrbl
3
- Version: 0.3.7.dev2
3
+ Version: 0.3.7.dev3
4
4
  Summary: A modern pure ASGI/WSGI Python framework for building schema-first REST and JSON-RPC APIs with SQLAlchemy models, typed validation, lifecycle hooks, and engine extension support.
5
5
  License-Expression: Apache-2.0
6
6
  License-File: LICENSE
@@ -1,11 +1,11 @@
1
1
  tigrbl/README.md,sha256=sc4ZkMwul4t62juRDcoKmhXeMKT_0V3tyMgLUC1EAbo,3755
2
- tigrbl/__init__.py,sha256=BBM8KozUXxmt1vtPYGrorj9QG1SHBN7bhDer_ANI8Kk,4432
2
+ tigrbl/__init__.py,sha256=mU-eaum7ErrG1l9TXPWIfHGt5WDoTfzZYF-cMryLgrk,4434
3
3
  tigrbl/api/__init__.py,sha256=uyhXi5nhYSu_jaJ0PcV5dBY9pLPFRcRq4bWDRNiYesc,376
4
4
  tigrbl/api/_api.py,sha256=O5LD6UTqiYx2AMQ819iQ91xFwJ-dHgcHHr-KAJ-22Qc,5490
5
5
  tigrbl/api/_route.py,sha256=L2pDzG5vvbGk-8cKC4zDZO6yS_uj-7LNsopXtZauCs8,3941
6
- tigrbl/api/_router.py,sha256=oLeI995s0ITb_io0BKWj1ljgrws94Pcd3IYg4jZoE5M,9299
6
+ tigrbl/api/_router.py,sha256=cbrcaoX2VUX8mZRV48Vx4UfT0GPArZT1G7O54yDeEKA,9290
7
7
  tigrbl/api/_routing.py,sha256=36THKKRpp50gDwq1WLPa7GVLS4Qb5XmD7WPoMg9ADqc,5077
8
- tigrbl/api/api_spec.py,sha256=ESSts5TW2pb4a9wU9H6drmKeU7YbZwx_xPaQ54RFuR4,991
8
+ tigrbl/api/api_spec.py,sha256=A6sJNc2hgYvxPsLtwARmlBo9a1efRFxJhSnXlDuVtKg,992
9
9
  tigrbl/api/mro_collect.py,sha256=V9U62GsBKDJ0R0N-8f2lVAEpjaO7i74LUcNliwnW-Co,1598
10
10
  tigrbl/api/resolve.py,sha256=MeUTwxlgG1aw4RWuQJ7RjWuqT-Sp4897eQ7VirqGDQY,5766
11
11
  tigrbl/api/shortcuts.py,sha256=_Ha6yQilo8siXQrSd3ALiK6IzFmgL_OePqs5gy9bFRs,1454
@@ -14,11 +14,11 @@ tigrbl/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
14
  tigrbl/app/_app.py,sha256=f8l0rseF2vyPnD9yjRhOaxFrMI8vxtlHiTJtByH0x9Q,4581
15
15
  tigrbl/app/_model_registry.py,sha256=v6UoDd7hmv5J4DpNEV13BOgIKIcKou10w3zyZVPd-po,1506
16
16
  tigrbl/app/_routing_runtime.py,sha256=cTCUoIdhDxg53DcXhBHev95fbDaJt0SDJHO5OEzW6Yo,524
17
- tigrbl/app/app_spec.py,sha256=kERebWxQtnaq9f_uMDNe4k_uJH8WbGRplP45P5tu90M,1471
17
+ tigrbl/app/app_spec.py,sha256=hd59FAlQsmhYejgM5cmP675YFTin287iuEUZa9IQuIc,1472
18
18
  tigrbl/app/mro_collect.py,sha256=W-kOoKGUSW9dtsKaPuMkN5sLBw_0-H8MTqps1KPwPyI,3351
19
19
  tigrbl/app/shortcuts.py,sha256=PEMbmwaqW8a3jkcHM5AXsYA2mmuXGFpOTnqlJnUX9Lo,1775
20
20
  tigrbl/app/tigrbl_app.py,sha256=YdJKy-Nv9YfYpeHU6_X_qNBkOQQik9gSloEz32xSMi8,19021
21
- tigrbl/bindings/__init__.py,sha256=GcM5PTKNLS1Jx__OiAcjLI5yybL40RrA8OzMVPno4_U,2643
21
+ tigrbl/bindings/__init__.py,sha256=ilLQ7lbrh_n9vJPPs69Dtt6Bc8i1QPfaebHSo4ZZ2rs,2644
22
22
  tigrbl/bindings/api/__init__.py,sha256=ayw4ZBCvvUThQm9PXS6TyVkV1MEYY8YwGcyT4HwWZ7c,395
23
23
  tigrbl/bindings/api/common.py,sha256=cOmr9oUiPvdydZpCYjLJ0ACJHrUQJWL0oZ7H00L7cSs,3593
24
24
  tigrbl/bindings/api/include.py,sha256=iJ20n74klX2M-6xv02FkuePKTSrGxufiZhAY8G_Ous0,10593
@@ -38,14 +38,14 @@ tigrbl/bindings/model_registry.py,sha256=ACMOIiQbyLs-89BwSNjY6iAS6xR8_vUdl0OcuT1
38
38
  tigrbl/bindings/rest/__init__.py,sha256=u3vD4v9bhD8wotjt7e2Wt46pmGOC5BWHo9co9KOYLxQ,193
39
39
  tigrbl/bindings/rest/attach.py,sha256=Vvw5qc3y390ZiAdj6UxZqI9S_p6v4o3e9znkfGJ3Ly0,1030
40
40
  tigrbl/bindings/rest/collection.py,sha256=PJ3PFcLirZq_niZbC6YZo0o-7QP6xIeVCKRatUUVIvA,12675
41
- tigrbl/bindings/rest/common.py,sha256=UfD-SpK9hSCkuon6hqIvfdssQeTK1MreGXDo2JUwvxk,3200
42
- tigrbl/bindings/rest/fastapi.py,sha256=-RAWA7zFJHYe2fzdCfYAwL3kbtP-9m4ki__fn3z9ps8,1212
43
- tigrbl/bindings/rest/helpers.py,sha256=HvOHYO9D9ZEFvUvnlCYyE4X78pe7mlqdXQPT8UrGHTg,3780
44
- tigrbl/bindings/rest/io.py,sha256=LIJG3aiepHyiQH951t-DqJFS_jkxk8TGIEbOLU83rJ0,11235
41
+ tigrbl/bindings/rest/common.py,sha256=SoZYmVcncYzEtc-MoGvccrWeMDzbErTWDMiToT0-LqA,3181
42
+ tigrbl/bindings/rest/fastapi.py,sha256=VZvTD7v57vlRHZX3JGyI8bZyPq8sZO9q5qgeMV8gCSw,1196
43
+ tigrbl/bindings/rest/helpers.py,sha256=NsP5H3qdrx87VOUF4Mrx41gNodtoOQIAjTIoYvL3_Qc,3771
44
+ tigrbl/bindings/rest/io.py,sha256=Hj87SUYzirdTxSdyJBWxa5y0tsFzcpUKKx9Yvnns1L8,11226
45
45
  tigrbl/bindings/rest/io_headers.py,sha256=cBI9YildYSr_W_4nxBawa2wsDU75nNNoFm3Hq74GRY0,1658
46
46
  tigrbl/bindings/rest/member.py,sha256=wwlZ1qvgD9tLkdq0-mB7Ltyx_vSGCEYMAduAHn6Wcvs,13081
47
47
  tigrbl/bindings/rest/router.py,sha256=6IyvnKTkbWK_FtIqQM8FJlTXj_2nshzq6IFLJYTLLnE,12744
48
- tigrbl/bindings/rest/routing.py,sha256=Grs8GOevdxnipBVmwT2nZ8CIihJvK8mEB_RNjlstAOQ,4666
48
+ tigrbl/bindings/rest/routing.py,sha256=_CyevMx6sArmVuNH2zLiMIbomkrWvKUBtqMvoPqFLLI,4657
49
49
  tigrbl/bindings/rpc.py,sha256=cmjGxOwQ3FxrIeQ8U25RvXGitNV06rdLO5rxpY_BfG8,13906
50
50
  tigrbl/bindings/schemas/__init__.py,sha256=50vm4LQXdszYLOUa_6E-uBE7M4dUGAD3cQXMFfsJmDo,260
51
51
  tigrbl/bindings/schemas/builder.py,sha256=yuoKxC2BbvyBBp-rpxPQKGN3NzAE2uy1gh_jDi__GaE,13063
@@ -81,8 +81,8 @@ tigrbl/core/crud/helpers/model.py,sha256=I_lcrJxeohEyFoptDsdxoAd6qJXc7pmzDjp7dbs
81
81
  tigrbl/core/crud/helpers/normalize.py,sha256=aDavCusuBDRltFX4baBlztDZ6dvgAG4Qs-DJQnNkfwc,2887
82
82
  tigrbl/core/crud/ops.py,sha256=Qu0A4KNX0LuQdUXC5NcqzaN1aP49_ank4czEHArXcsQ,7900
83
83
  tigrbl/core/crud/params.py,sha256=vc9b7tcqm_a376IO7UPcKrdl7lXqrhsqYTvmu5j9ucQ,1095
84
- tigrbl/core/resolver.py,sha256=m1C9bx6jiYzhWtS1CBliKjDtoCpLvK080cD_0EuvfLY,2552
85
- tigrbl/core/router_runtime.py,sha256=qEQg2-oPgbAMm8OYaoBAQP4oJof056B44NvB2BRdiY8,5821
84
+ tigrbl/core/resolver.py,sha256=7JT7Nt9tT0uTHOVOUqns0uYQYUGf9XztL3TFysrloN0,2543
85
+ tigrbl/core/router_runtime.py,sha256=AwSsDVz6eps-zYEOYybCVHH5T1sOf78zWA-ksjV-_YQ,5802
86
86
  tigrbl/ddl/__init__.py,sha256=JJ-P7tluzS9U_0M-WBiBLOSi9I3Id9MN0eeN4ENgwTE,11557
87
87
  tigrbl/decorators.py,sha256=U4iss3iN--4PPGJ90doAcYn4WuDx1dQMw-5Ar4SE7TU,445
88
88
  tigrbl/deps/__init__.py,sha256=LBotr2u9BR_DInsmbqMMhJ41_vdV4c1gK7X_P2Q6GGk,758
@@ -103,8 +103,8 @@ tigrbl/engine/plugins.py,sha256=UveqYo9VlDn4Y7xgGvsHi_O1ugd0kBYxIuyFMSDWoug,1497
103
103
  tigrbl/engine/registry.py,sha256=MmTe7job1W9k18-g0fx6rZzVU9-D_5fElW9TXo0_HxA,1113
104
104
  tigrbl/engine/resolver.py,sha256=J5iuYW_vttYJH1-4hlevsHQlCC1x_OvGKLGtcLQVYEE,11467
105
105
  tigrbl/engine/shortcuts.py,sha256=JXGzGO1yeBSiHMj96xB9JOqwv5ZWWgUaD4Plfq0WICU,6809
106
- tigrbl/headers/__init__.py,sha256=YjZqkkTRIG6lrQGudcdGpIA9jOqYKU4Q3a6lEfHF6SY,52
107
- tigrbl/headers/_header.py,sha256=EShnAs5bpW5tCAOlLQccjOlnuwH90_XckeJ4eFe8J14,2032
106
+ tigrbl/headers/__init__.py,sha256=a5qqUeLbcod9ojc0VWY3X_YRVAXoc8KN-NL9QPayOjo,84
107
+ tigrbl/headers/_header.py,sha256=TnPGi5bM5EvTJuALVW1LiuAinD0Twg_4NlWTQu2uGIQ,2748
108
108
  tigrbl/hook/__init__.py,sha256=cslI8hZVwsQu9MgpXyjU84cqR1DLLU7TkajPSYptkh8,523
109
109
  tigrbl/hook/_hook.py,sha256=goaEq7ktHjin7bjrFKdynCjnVkG59Pnxe6XzPEABFJY,470
110
110
  tigrbl/hook/decorators.py,sha256=nY91zc0yuwcS9g_Kka9QlNVxvhqFZUwaC_LWqkemxkA,1033
@@ -127,7 +127,7 @@ tigrbl/op/decorators.py,sha256=6YvoFWvrpaD_pMKVwQkkjs_mtm6K-AD5PQot0Q9CRjM,7071
127
127
  tigrbl/op/model_registry.py,sha256=RImaObQhtFvKoiPJ0WJLdh9SRozDTF9silRsOg6KNdo,9345
128
128
  tigrbl/op/mro_collect.py,sha256=Y50Z90VE4ABopq3v3PKfUdx2oYCStWWHrCZRI38fOHY,3259
129
129
  tigrbl/op/resolver.py,sha256=iGnBWrjQWRFov-D3gVizGcZN7gCq_k2liZd5RwWBZWg,6700
130
- tigrbl/op/types.py,sha256=Q_OScUhbjV48V2VcyLQ248s5La0OjPCaGu2dIUR7o88,4880
130
+ tigrbl/op/types.py,sha256=hx7zG0kH_XctZBa4mCJqBx00lMcrj67uu_4jTvpdnU0,4881
131
131
  tigrbl/orm/__init__.py,sha256=oQVk8swfAop9oNNC-WVf0_OdAmXRf7EjqIQfW7w_fLA,61
132
132
  tigrbl/orm/mixins/_RowBound.py,sha256=a2B7USX8fx6-kyVnQdoEW3CZCKxAwC5jWCfR-enpkCg,3054
133
133
  tigrbl/orm/mixins/__init__.py,sha256=X1qWzDcr_wSNNOo7JJ_hpHGR_tO1HsqDF7vHh6N-xNs,1914
@@ -156,18 +156,16 @@ tigrbl/orm/tables/rbac.py,sha256=RKLusnX2TwKbO44vIHsg5XdoW8zYm7hoBPJzBuWWXkc,191
156
156
  tigrbl/orm/tables/status.py,sha256=Eb73GsibghSqqY_lzhsO6OxQSYvlfRTc0PnO38nDtYw,3578
157
157
  tigrbl/orm/tables/tenant.py,sha256=bTNkg36A1EeRX2hQuRCUr6J78RkFdm0BgUGHNWCnOHI,450
158
158
  tigrbl/orm/tables/user.py,sha256=J0ix0hb8UgcCjoAQhSLn-XN8HR8TTt3mmlwH0bAjTJs,789
159
- tigrbl/requests/__init__.py,sha256=BzXlz3DV41Mo2e5ompkFMlP6qAbttGcKlop67zt7ttQ,99
160
- tigrbl/requests/_request.py,sha256=8P4MBmdvv7Qp5xAeX1vU_OzJKjgWuMkMFRIAbbgxknw,3316
161
- tigrbl/response/README.md,sha256=SVfrSPT7RuZMy2JmyxL9S3bOvhGdDsZQANH-grH37FI,1758
162
- tigrbl/response/__init__.py,sha256=fBLd8ECzEblZMiYCP5tNEMF01ZqXjnSEDsLalDfI55U,1232
163
- tigrbl/response/bind.py,sha256=fj5deXrOBn-jauvW0j2yNdVziOBvPOvbOklFQFeoyYk,293
164
- tigrbl/response/decorators.py,sha256=x48FaZkjZ_xZq_v6tS9fJqVXuVvVB1wg_j95MqnejUY,1203
165
- tigrbl/response/resolver.py,sha256=ZCcQiBxzZuC_iteOQVcQdLBQmY8wSSJ2HYryb2YzzUA,2532
166
- tigrbl/response/shortcuts.py,sha256=LiwzJoMFtpEApXz6mWVypn4KnR9gwu-g3dmHQqs_-0Q,4522
167
- tigrbl/response/stdapi.py,sha256=im-DAlGq0QeDBI1K8kjB5M5aMrJcdprJylOYqwZc_FU,492
168
- tigrbl/response/types.py,sha256=110etKRHl0jnnhwFPCWwl6DawQ7cOel1yq4lo3r9KIk,1329
169
- tigrbl/responses/__init__.py,sha256=l384cL7MNjm4S-N9PtHJwFpn1YqipIxzlBef0zu_w6A,328
170
- tigrbl/responses/_response.py,sha256=YdGdxFGUu4ODpXfpwAyETBGvy81pVDW5rWgq1lgnTSY,6813
159
+ tigrbl/requests/__init__.py,sha256=pEBf7c5849tAjL7kSuh9BQKtSukxR9Hs7Tq5TGhaEHY,146
160
+ tigrbl/requests/_request.py,sha256=DKpEXl7JyzMBKpKoNEwzchTgujEeEiA3qPNBFAwY63E,4620
161
+ tigrbl/responses/README.md,sha256=SVfrSPT7RuZMy2JmyxL9S3bOvhGdDsZQANH-grH37FI,1758
162
+ tigrbl/responses/__init__.py,sha256=G7QTAdjcFtcAgQBF4QOEc6ckrMgUUt7k-F96EiMUql8,1265
163
+ tigrbl/responses/_response.py,sha256=qI_7w03iJ21szPUOxsstzyD-92n2-hdi4AvV3xc0Eck,7433
164
+ tigrbl/responses/bind.py,sha256=fj5deXrOBn-jauvW0j2yNdVziOBvPOvbOklFQFeoyYk,293
165
+ tigrbl/responses/decorators.py,sha256=x48FaZkjZ_xZq_v6tS9fJqVXuVvVB1wg_j95MqnejUY,1203
166
+ tigrbl/responses/resolver.py,sha256=ZCcQiBxzZuC_iteOQVcQdLBQmY8wSSJ2HYryb2YzzUA,2532
167
+ tigrbl/responses/shortcuts.py,sha256=y1CR7CvMk79WuxxpNMDqEDK7_QYDokeBePuuWaKu8cI,4525
168
+ tigrbl/responses/types.py,sha256=110etKRHl0jnnhwFPCWwl6DawQ7cOel1yq4lo3r9KIk,1329
171
169
  tigrbl/rest/__init__.py,sha256=XmCGc3HdPqRJfzehJj1MfhpJFlheDMMlVSZz1EE2yZQ,824
172
170
  tigrbl/runtime/README.md,sha256=0sCqYWNPcdG6tFA7fVyri2DVbuTsiw3T5WwnTzF-i78,5956
173
171
  tigrbl/runtime/__init__.py,sha256=M5Fi94al8xiIS2xK_4OFLi6Aq4aJsE1GuwehZZnv268,449
@@ -184,13 +182,13 @@ tigrbl/runtime/atoms/resolve/__init__.py,sha256=xRdUnSxI5k3mUgVbx9VBRyrbJM3uKeHS
184
182
  tigrbl/runtime/atoms/resolve/assemble.py,sha256=Pzs8Pm_8trAB17saAfezgZUM4IPk4kIMrahZKgFRKac,6301
185
183
  tigrbl/runtime/atoms/resolve/paired_gen.py,sha256=Jj4v5Wg3zfdv3omv-3DG8c93I6ynEbxHhJFza_SE-Mc,5676
186
184
  tigrbl/runtime/atoms/response/__init__.py,sha256=9bDiy1eX9TAT7SnRkUOtQEuI_P-MRU1sRiXuPHvIXXY,679
187
- tigrbl/runtime/atoms/response/headers_from_payload.py,sha256=kuMwkvyQYyX_voFRNIfYqKWb1EqeG5N7HVn_j9co0gw,1705
185
+ tigrbl/runtime/atoms/response/headers_from_payload.py,sha256=RZ43g0xJ2YWCidJb4IabqT300hXCTYKdJwr23KyFEMc,1706
188
186
  tigrbl/runtime/atoms/response/negotiate.py,sha256=G6QPl_U1Oz8csFpMitSlvzAvUsVLzc-fo-G3eJX2jak,913
189
187
  tigrbl/runtime/atoms/response/negotiation.py,sha256=G2wy6Enr93nuLJVELFdY5W6J14G6gsEvHZi64YSbp-o,1125
190
188
  tigrbl/runtime/atoms/response/render.py,sha256=uo3laUH8d4-9dCP4LevfxeysLCs0UxZDjSJuznHPpsc,970
191
- tigrbl/runtime/atoms/response/renderer.py,sha256=4UzV5cZpRSLZbq_5meE7p4WncKyFXY3OWM831UbNV3s,2955
189
+ tigrbl/runtime/atoms/response/renderer.py,sha256=1_O_xb3G9gdGOoTs_Gx0d3O8HtcNetacd4zNWqRTsx0,2946
192
190
  tigrbl/runtime/atoms/response/template.py,sha256=iTonG7AAQpOYfpOuh8LCsKEFNLOTYCFI4zt6Ih9NygE,1240
193
- tigrbl/runtime/atoms/response/templates.py,sha256=ZGTJ_uw3ra20aIStgga7S62pHAd8rAnr7WIZAbDozxI,2792
191
+ tigrbl/runtime/atoms/response/templates.py,sha256=oJDBlLIz5s6Jxqd5ilX5zC4knnu8kZFjOWpS05KFqEc,2783
194
192
  tigrbl/runtime/atoms/schema/__init__.py,sha256=uFxw3EUDHar1MyULDzWEASDJTrPlA-EMvKv3C5hZCdc,1370
195
193
  tigrbl/runtime/atoms/schema/collect_in.py,sha256=G0j_3gT1w-Weej07m9qpKYGYltOxQFjekh0zMIBhxug,545
196
194
  tigrbl/runtime/atoms/schema/collect_out.py,sha256=QRmeqvcdxagNhkideRelBkcxD8mXIQuZZMy16piM6og,534
@@ -207,7 +205,7 @@ tigrbl/runtime/executor/__init__.py,sha256=JuFfQo0xo9_C8XW014vdEtMvzNKaugXhLg8tW
207
205
  tigrbl/runtime/executor/guards.py,sha256=HiE9J_Y-AslRJZYBrjZmGFecq9Tx4nn6HMqL1jrezLY,4210
208
206
  tigrbl/runtime/executor/helpers.py,sha256=PU7Bj7VTdGaldNYuBRsrV4408G4UqxJIWwPUwtUgNaA,2239
209
207
  tigrbl/runtime/executor/invoke.py,sha256=oA9Ca8c_iSnlawYrlOH5RTpA5cUlex6Ji18fX4AJd7U,4754
210
- tigrbl/runtime/executor/types.py,sha256=7VVfgv_ol99L9RV8KlCfPqUy0SWmv4deWFOn_N39epU,2142
208
+ tigrbl/runtime/executor/types.py,sha256=xfa7ScQnwlYiEBUYjX-VbdpOx-y_xh2SSKDDhiKkpLY,2133
211
209
  tigrbl/runtime/kernel.py,sha256=cL1bnw2glp33AjiFchMg8MZsJ3lb7c2JPhz26JejP1I,22895
212
210
  tigrbl/runtime/labels.py,sha256=aBXx65lMPlR60wXH4M_xeuXN4w3z07ep6NxICAiSy68,13134
213
211
  tigrbl/runtime/opview.py,sha256=UHbQbGg5GGi2AWFip8qBUD6fe6h8VNU232GRly1aT-I,2792
@@ -257,7 +255,7 @@ tigrbl/shortcuts.py,sha256=wa3AlX4r4IbzAkxANeRsIbFdy_zu0s6xFdTKZsAohyM,712
257
255
  tigrbl/specs.py,sha256=SBGeNuzmyU4t0TyrDQlZg_MElC7B6i--ghGEgSIX3vU,1163
258
256
  tigrbl/system/__init__.py,sha256=rTQQCG-O6WUx5C7bo5ukaRRCKl06DA39PhJ_QZpaF-0,1922
259
257
  tigrbl/system/diagnostics/__init__.py,sha256=QtQfmaE7LreWKH2Zggmh8kIAilzhF1gQYdhNOVAGuCE,717
260
- tigrbl/system/diagnostics/compat.py,sha256=XoAzeSfBhqfdnUPU_bj-6qY2JSPJT47tqXcFsuaQGzM,995
258
+ tigrbl/system/diagnostics/compat.py,sha256=0pvg_ghxYY3ZkGqVX_0RgQlhLE0t3-v24JZIAUlfTSA,985
261
259
  tigrbl/system/diagnostics/healthz.py,sha256=vt_v5L-IJfU8YOIpb2yMnTL2_lUNVBdcMK3JwQG0CBs,1316
262
260
  tigrbl/system/diagnostics/hookz.py,sha256=q8Fl7yPPdcEw2JkZNAEMhxKwd7EezAfdPNmAZt7E7C0,1781
263
261
  tigrbl/system/diagnostics/kernelz.py,sha256=MuM7-95yaIu28nZ3KOWYhfbYeb489Ep6ucmRTqL3JMA,433
@@ -265,39 +263,38 @@ tigrbl/system/diagnostics/methodz.py,sha256=kYOpKGuhPjJV5bW13-e4jHM4GQelsoc3Vf2L
265
263
  tigrbl/system/diagnostics/router.py,sha256=5RAv6LPoN4luGwIPnYGak66uT9FslYPc-d_GKqc4S8c,1854
266
264
  tigrbl/system/diagnostics/utils.py,sha256=qxC8pUNK2lQKh5cGlF-PSFA-cfJFLlAHW0-iEosvPgg,1172
267
265
  tigrbl/system/docs/__init__.py,sha256=uaM5a5vERp_bO68LfCaSCII_Rs4F9AAzfmbvA-abcjQ,967
268
- tigrbl/system/docs/lens.py,sha256=qEjC7X0nu9dQ29zSOPVjHiibP3Q0niXL-pSybnAjGog,2522
266
+ tigrbl/system/docs/lens.py,sha256=ych2PADP6e__H8v4964iJFuQCjytXSNzH1Sk_w5A7jI,2512
269
267
  tigrbl/system/docs/openapi/__init__.py,sha256=9aN4AuBuNoJsJ2Oz750NsJY2B_wg2KEQBOdJbwiTZGg,897
270
268
  tigrbl/system/docs/openapi/helpers.py,sha256=sRUhy2FINaerUQ-cecti4Qaw5WvkRKfaPLYit0vy8Vk,6460
271
269
  tigrbl/system/docs/openapi/metadata.py,sha256=OY0lfMsgvNH0VvdzGqKqaPNjC4QxHd-GqajXgmXwnqI,608
272
- tigrbl/system/docs/openapi/mount.py,sha256=btAIAr-vXp9Q4fF7-Y1o-uxFM-bGoQCNNk-tBJGtMQ8,607
270
+ tigrbl/system/docs/openapi/mount.py,sha256=ALMZSmYKiMECKr7Z8O7TDZcYGqnFiKniDdLWC0fa2Dw,597
273
271
  tigrbl/system/docs/openapi/schema.py,sha256=f20VMG9_LusTLkzFMPMItsO4HRjSGvbkA0xWH1fiQ4A,5127
274
- tigrbl/system/docs/openrpc.py,sha256=qCzJ-fFpwI2oRgYt6YYkPQ3wx2WZjeWIPxAj00FC3SI,4460
275
- tigrbl/system/docs/swagger.py,sha256=x1gATEGCIEqLNXLehqyqdf1E8k9A1i9BxMihF7_nIn8,1872
276
- tigrbl/system/favicon/__init__.py,sha256=AYERJhru5Z0zYFuJYyGhCrY5IDgmU12CoeOUE6tYXk0,1037
272
+ tigrbl/system/docs/openrpc.py,sha256=a4C-8EzIferFzNZdpn2WRdMcQ3VXJnB_0k9AVetpCLw,4450
273
+ tigrbl/system/docs/swagger.py,sha256=u7zP4mzU3Nv6OB0_BE6XlYOxB8L6sy6kqsFCOvdc0cY,1862
274
+ tigrbl/system/favicon/__init__.py,sha256=eeiwkVjicaP6zKyX8zb3KmTK8ysvbZaze5-W9C2iocg,1027
277
275
  tigrbl/system/favicon/assets/favicon.svg,sha256=C_S8UPLuLx4QIdP84OpzKw7IiwWjrHIzyKotoUKRLB4,195
278
276
  tigrbl/system/uvicorn.py,sha256=ogvIqfv-1CxAPZ8BADucaNAy_ePsLA3IVIZxmhdfL3A,1526
279
277
  tigrbl/table/__init__.py,sha256=yyP9iZBUJh-D7TCy9WQIvMXKL3ztyX-EXdTK4RTE7iw,207
280
- tigrbl/table/_base.py,sha256=k2ZEaMCygpoCMFEwOC2L02oe4cR8W3zwn2NInZcIcHI,18170
278
+ tigrbl/table/_base.py,sha256=1oRYSzvV04jndlehc6HWFO4PQcBPiAjvjzGVfnZouSY,18171
281
279
  tigrbl/table/_table.py,sha256=B7ft2SMnbp3aTWKO44M4EWTHmzFKyQlpdj-3QULRaGk,1740
282
280
  tigrbl/table/mro_collect.py,sha256=PbuSZnUvVbs3NCe2otie7pvcjxeF54hSiVp_VywclSA,2733
283
281
  tigrbl/table/shortcuts.py,sha256=-IZAZyMTsiCdKV0w7nq1C2YBsB6iE_uNGJb-PatlO8I,1716
284
- tigrbl/table/table_spec.py,sha256=dvilrGWX7fVc6ThTbAqJKxxl3r6_MKNFY0cs_wuyvC8,1001
282
+ tigrbl/table/table_spec.py,sha256=QpF6Twx9yd_ABpFI1ZzsrBIF3ns7RtaAPF5zD05zeWI,1002
285
283
  tigrbl/transport/__init__.py,sha256=0bX4qEE3x_pKnGmvye54BMri1l4tnbYzian2Sa6_8Tw,3560
286
- tigrbl/transport/asgi_wsgi.py,sha256=FZjOT7LR90o5GCprlm4y32OrKFnZtuj55kvvMGImm3c,7794
284
+ tigrbl/transport/asgi_wsgi.py,sha256=uVQ_b8uqol2BN4beFjio5ODx4VF-I5qOhD8zpvvWzHo,7778
287
285
  tigrbl/transport/background.py,sha256=TDFPKQ2D0qriMDNYrCI5wvwv1Iav2D31tjpSwI93aAI,769
288
286
  tigrbl/transport/httpx.py,sha256=UH9CyWxDvOELsF5ob2sRiZGzzxSvV89_J_pXLNw4bmE,611
289
287
  tigrbl/transport/jsonrpc/__init__.py,sha256=yIuAjf3zQsqezvS0TdzDlfIhtuzMi9_ykSIqjn-w8EM,618
290
- tigrbl/transport/jsonrpc/dispatcher.py,sha256=YrC9g3wsrIWxVi5sojPjG51QjQSYDP36WpXPs2yQeMA,13708
288
+ tigrbl/transport/jsonrpc/dispatcher.py,sha256=tlI3W-n1aUjvUjEDH_WGmFFrZK9QPuEHWF0IOeweAgE,14452
291
289
  tigrbl/transport/jsonrpc/helpers.py,sha256=oyqx36m8n7EofciPVvTEM9Pz1l51zJwsI224AXkE7q8,3010
292
290
  tigrbl/transport/jsonrpc/models.py,sha256=omtjb-NN8HyWgIZ5tHafEsbwC7f1XlttAFHFA41Xn2k,973
293
291
  tigrbl/transport/jsonrpc/openrpc.py,sha256=EoIwdDiwO_awUGTCO_CnBuEGsGW8QYYEOSMVExtPsNY,88
294
- tigrbl/transport/request.py,sha256=1OyLH2p-A1-OMorIcD9MKlujNNu8R0HuVBkVX8WtGPU,249
295
292
  tigrbl/transport/rest/__init__.py,sha256=GMLVazXKQnSEVocVXtOGpsDDduXvv7HdK2iaokd03e8,890
296
293
  tigrbl/transport/rest/aggregator.py,sha256=iQ__kin4PlEgFmDPM6PshfDbj-bw8m0AC62hO22IVRk,4445
297
294
  tigrbl/transport/rest/decorators.py,sha256=KRWTpZaU3KTYbg4ZTtqGsSKuIsiThEC1JFL7WBAsaic,928
298
- tigrbl/types/__init__.py,sha256=ji2LXQjXBOP9HjwEXCEMPN7Gu2rnuU8D4jJOLHMtINU,4270
295
+ tigrbl/types/__init__.py,sha256=D4VRagC8gxpbYO8Vei3gIoCgrG7cqwbVeCv15NfSXfE,4251
299
296
  tigrbl/types/allow_anon_provider.py,sha256=5mWvSfk_eCY_o6oMm1gSEqz6cKyJyoZ1-DcVYm0KmpA,565
300
- tigrbl/types/authn_abc.py,sha256=2FiB0HRfeROeFfVspVCRnW3CxAnDxhX031IvolC9djE,779
297
+ tigrbl/types/authn_abc.py,sha256=9ImWU4TmDrgVLfyrHWXqfmg21m3zJZ6vWTbXam9KoRg,770
301
298
  tigrbl/types/nested_path_provider.py,sha256=1z-4Skz_X_hy-XGEAQnNv6vyrfFNsPIvlhBqf457Sjc,609
302
299
  tigrbl/types/op.py,sha256=-kdDABEyIBEAMMtatOXVPeMEaGFUiXtJSStZkNu3cnA,769
303
300
  tigrbl/types/op_config_provider.py,sha256=tYH_py9niHvLQF9c9OD13Wt8U4hoYxUatJO_Zp5Cs8Q,551
@@ -306,7 +303,7 @@ tigrbl/types/request_extras_provider.py,sha256=JOIpzx1PYA2AYYvkMiXrxlwpBLOPD2cQa
306
303
  tigrbl/types/response_extras_provider.py,sha256=sFB0R3vyUqmpT-o8983hH9FAlOq6-wwNVK6vfuCPHCg,653
307
304
  tigrbl/types/table_config_provider.py,sha256=EkfOhy9UDfy7EgiZddw9KIl5tRujRjXJlr4cSk1Rm5k,361
308
305
  tigrbl/types/uuid.py,sha256=pD-JrhS0L2GXeJ0Hv_oKzRuiXmxHDTVoMqExO48iqZE,1993
309
- tigrbl-0.3.7.dev2.dist-info/METADATA,sha256=dwf4WjcpG_wZ5Qoyzgd_TKZcGrle8wiMyVZxjiAk7Dc,24940
310
- tigrbl-0.3.7.dev2.dist-info/WHEEL,sha256=kJCRJT_g0adfAJzTx2GUMmS80rTJIVHRCfG0DQgLq3o,88
311
- tigrbl-0.3.7.dev2.dist-info/licenses/LICENSE,sha256=djUXOlCxLVszShEpZXshZ7v33G-2qIC_j9KXpWKZSzQ,11359
312
- tigrbl-0.3.7.dev2.dist-info/RECORD,,
306
+ tigrbl-0.3.7.dev3.dist-info/METADATA,sha256=it4fF3PFauXsvc0B19jYCRxQI2n3eB9d8mcSot90imI,24940
307
+ tigrbl-0.3.7.dev3.dist-info/WHEEL,sha256=kJCRJT_g0adfAJzTx2GUMmS80rTJIVHRCfG0DQgLq3o,88
308
+ tigrbl-0.3.7.dev3.dist-info/licenses/LICENSE,sha256=djUXOlCxLVszShEpZXshZ7v33G-2qIC_j9KXpWKZSzQ,11359
309
+ tigrbl-0.3.7.dev3.dist-info/RECORD,,
@@ -1,54 +0,0 @@
1
- from ..responses._response import (
2
- Response as StdApiResponse,
3
- JSONResponse,
4
- HTMLResponse,
5
- PlainTextResponse,
6
- StreamingResponse,
7
- FileResponse,
8
- RedirectResponse,
9
- )
10
- from .decorators import (
11
- response_ctx,
12
- get_attached_response_spec,
13
- get_attached_response_alias,
14
- )
15
- from .types import (
16
- Response,
17
- ResponseKind,
18
- ResponseSpec,
19
- Template,
20
- TemplateSpec,
21
- )
22
- from .resolver import resolve_response_spec, infer_hints
23
- from .shortcuts import as_json, as_html, as_text, as_redirect, as_stream, as_file
24
- from ..runtime.atoms.response.renderer import ResponseHints, render
25
- from ..runtime.atoms.response.templates import render_template
26
-
27
- __all__ = [
28
- "StdApiResponse",
29
- "JSONResponse",
30
- "HTMLResponse",
31
- "PlainTextResponse",
32
- "StreamingResponse",
33
- "FileResponse",
34
- "RedirectResponse",
35
- "response_ctx",
36
- "get_attached_response_spec",
37
- "get_attached_response_alias",
38
- "ResponseSpec",
39
- "ResponseKind",
40
- "TemplateSpec",
41
- "Response",
42
- "Template",
43
- "resolve_response_spec",
44
- "infer_hints",
45
- "as_json",
46
- "as_html",
47
- "as_text",
48
- "as_redirect",
49
- "as_stream",
50
- "as_file",
51
- "ResponseHints",
52
- "render",
53
- "render_template",
54
- ]
tigrbl/response/stdapi.py DELETED
@@ -1,26 +0,0 @@
1
- """Backward-compatible stdapi response exports.
2
-
3
- Prefer importing from ``tigrbl.responses``.
4
- """
5
-
6
- from tigrbl.headers import Headers
7
- from tigrbl.responses._response import (
8
- FileResponse,
9
- HTMLResponse,
10
- JSONResponse,
11
- PlainTextResponse,
12
- RedirectResponse,
13
- Response,
14
- StreamingResponse,
15
- )
16
-
17
- __all__ = [
18
- "Headers",
19
- "Response",
20
- "JSONResponse",
21
- "HTMLResponse",
22
- "PlainTextResponse",
23
- "StreamingResponse",
24
- "FileResponse",
25
- "RedirectResponse",
26
- ]
@@ -1,9 +0,0 @@
1
- """Backward-compatible request exports.
2
-
3
- Prefer importing from ``tigrbl.requests``.
4
- """
5
-
6
- from tigrbl.headers import Headers
7
- from tigrbl.requests._request import AwaitableValue, Request, URL
8
-
9
- __all__ = ["Headers", "Request", "AwaitableValue", "URL"]
File without changes
File without changes
File without changes
File without changes
File without changes