tigrbl 0.3.29.dev2__tar.gz → 0.4.0.dev2__tar.gz

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 (111) hide show
  1. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/PKG-INFO +10 -1
  2. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/README.md +4 -0
  3. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/pyproject.toml +10 -2
  4. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/__init__.py +58 -0
  5. tigrbl-0.4.0.dev2/tigrbl/canonical_json.py +7 -0
  6. tigrbl-0.4.0.dev2/tigrbl/session/__init__.py +8 -0
  7. tigrbl-0.4.0.dev2/tigrbl/session/base.py +7 -0
  8. tigrbl-0.4.0.dev2/tigrbl/session/spec.py +7 -0
  9. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/system/diagnostics/__init__.py +10 -0
  10. tigrbl-0.4.0.dev2/tigrbl/system/diagnostics/healthz.py +285 -0
  11. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/types/__init__.py +0 -16
  12. tigrbl-0.3.29.dev2/tigrbl/system/diagnostics/healthz.py +0 -69
  13. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/LICENSE +0 -0
  14. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/__main__.py +0 -0
  15. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/cli.py +0 -0
  16. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/config/__init__.py +0 -0
  17. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/config/constants.py +0 -0
  18. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/config/defaults.py +0 -0
  19. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/ddl/__init__.py +0 -0
  20. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/decorators/__init__.py +0 -0
  21. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/decorators/allow_anon.py +0 -0
  22. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/decorators/engine.py +0 -0
  23. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/decorators/hook.py +0 -0
  24. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/decorators/middlewares.py +0 -0
  25. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/decorators/op.py +0 -0
  26. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/decorators/response.py +0 -0
  27. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/decorators/rest.py +0 -0
  28. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/decorators/schema.py +0 -0
  29. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/decorators/session.py +0 -0
  30. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/engine/__init__.py +0 -0
  31. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/engine/bind.py +0 -0
  32. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/engine/builders.py +0 -0
  33. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/engine/capabilities.py +0 -0
  34. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/engine/collect.py +0 -0
  35. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/engine/plugins.py +0 -0
  36. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/engine/registry.py +0 -0
  37. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/engine/resolver.py +0 -0
  38. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/factories/__init__.py +0 -0
  39. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/factories/app.py +0 -0
  40. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/factories/column.py +0 -0
  41. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/factories/engine.py +0 -0
  42. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/factories/hook.py +0 -0
  43. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/factories/op.py +0 -0
  44. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/factories/responses.py +0 -0
  45. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/factories/router.py +0 -0
  46. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/factories/schema.py +0 -0
  47. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/factories/table.py +0 -0
  48. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/hook/exceptions.py +0 -0
  49. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/hook/types.py +0 -0
  50. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/middlewares/__init__.py +0 -0
  51. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/middlewares/compose.py +0 -0
  52. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/op/__init__.py +0 -0
  53. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/op/canonical.py +0 -0
  54. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/op/collect.py +0 -0
  55. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/op/types.py +0 -0
  56. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/orm/__init__.py +0 -0
  57. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/requests.py +0 -0
  58. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/rest/__init__.py +0 -0
  59. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/schema/__init__.py +0 -0
  60. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/schema/builder/__init__.py +0 -0
  61. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/schema/builder/build_schema.py +0 -0
  62. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/schema/builder/cache.py +0 -0
  63. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/schema/builder/extras.py +0 -0
  64. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/schema/builder/helpers.py +0 -0
  65. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/schema/builder/list_params.py +0 -0
  66. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/schema/builder/strip_parent_fields.py +0 -0
  67. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/schema/utils.py +0 -0
  68. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/security/__init__.py +0 -0
  69. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/security/dependencies.py +0 -0
  70. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/shortcuts/__init__.py +0 -0
  71. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/shortcuts/app.py +0 -0
  72. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/shortcuts/column.py +0 -0
  73. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/shortcuts/engine.py +0 -0
  74. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/shortcuts/hook.py +0 -0
  75. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/shortcuts/op.py +0 -0
  76. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/shortcuts/responses.py +0 -0
  77. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/shortcuts/rest.py +0 -0
  78. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/shortcuts/router.py +0 -0
  79. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/shortcuts/schema.py +0 -0
  80. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/shortcuts/table.py +0 -0
  81. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/specs.py +0 -0
  82. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/system/__init__.py +0 -0
  83. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/system/diagnostics/hookz.py +0 -0
  84. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/system/diagnostics/kernelz.py +0 -0
  85. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/system/diagnostics/methodz.py +0 -0
  86. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/system/diagnostics/router.py +0 -0
  87. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/system/diagnostics/utils.py +0 -0
  88. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/system/docs/__init__.py +0 -0
  89. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/system/docs/lens.py +0 -0
  90. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/system/docs/openapi/__init__.py +0 -0
  91. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/system/docs/openapi/helpers.py +0 -0
  92. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/system/docs/openapi/metadata.py +0 -0
  93. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/system/docs/openapi/mount.py +0 -0
  94. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/system/docs/openapi/schema.py +0 -0
  95. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/system/docs/openrpc.py +0 -0
  96. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/system/docs/swagger.py +0 -0
  97. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/system/favicon/__init__.py +0 -0
  98. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/system/favicon/assets/favicon.svg +0 -0
  99. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/system/uvicorn.py +0 -0
  100. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/transport/__init__.py +0 -0
  101. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/transport/jsonrpc/__init__.py +0 -0
  102. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/transport/jsonrpc/helpers.py +0 -0
  103. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/transport/jsonrpc/models.py +0 -0
  104. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/transport/rest/__init__.py +0 -0
  105. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/transport/rest/aggregator.py +0 -0
  106. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/utils/__init__.py +0 -0
  107. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/utils/schema.py +0 -0
  108. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/vendor/__init__.py +0 -0
  109. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/vendor/jinja.py +0 -0
  110. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/vendor/pydantic.py +0 -0
  111. {tigrbl-0.3.29.dev2 → tigrbl-0.4.0.dev2}/tigrbl/vendor/sqlalchemy.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tigrbl
3
- Version: 0.3.29.dev2
3
+ Version: 0.4.0.dev2
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
@@ -40,6 +40,11 @@ Requires-Dist: tigrbl-orm
40
40
  Requires-Dist: tigrbl-runtime
41
41
  Requires-Dist: tigrbl-tests ; extra == "tests"
42
42
  Requires-Dist: uvicorn
43
+ Project-URL: Discord, https://discord.gg/K4YTAPapjR
44
+ Project-URL: Homepage, https://github.com/tigrbl/tigrbl
45
+ Project-URL: Issues, https://github.com/tigrbl/tigrbl/issues
46
+ Project-URL: Organization, https://github.com/tigrbl
47
+ Project-URL: Repository, https://github.com/tigrbl/tigrbl
43
48
  Description-Content-Type: text/markdown
44
49
 
45
50
  # tigrbl
@@ -60,6 +65,10 @@ It is not the authoritative location for repository governance, current target s
60
65
 
61
66
  ## Package identity
62
67
 
68
+ - canonical repository: `https://github.com/tigrbl/tigrbl`
69
+ - organization: `https://github.com/tigrbl`
70
+ - social: `https://discord.gg/K4YTAPapjR`
71
+ - package path: `https://github.com/tigrbl/tigrbl/tree/master/pkgs/core/tigrbl`
63
72
  - workspace path: `pkgs/core/tigrbl`
64
73
  - workspace class: core Python package
65
74
  - implementation layout: `tigrbl/`
@@ -16,6 +16,10 @@ It is not the authoritative location for repository governance, current target s
16
16
 
17
17
  ## Package identity
18
18
 
19
+ - canonical repository: `https://github.com/tigrbl/tigrbl`
20
+ - organization: `https://github.com/tigrbl`
21
+ - social: `https://discord.gg/K4YTAPapjR`
22
+ - package path: `https://github.com/tigrbl/tigrbl/tree/master/pkgs/core/tigrbl`
19
23
  - workspace path: `pkgs/core/tigrbl`
20
24
  - workspace class: core Python package
21
25
  - implementation layout: `tigrbl/`
@@ -1,10 +1,9 @@
1
1
  [project]
2
2
  name = "tigrbl"
3
- version = "0.3.29.dev2"
3
+ version = "0.4.0.dev2"
4
4
  description = "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 = "Apache-2.0"
6
6
  readme = "README.md"
7
- repository = "http://github.com/swarmauri/swarmauri-sdk"
8
7
  requires-python = ">=3.10,<3.14"
9
8
  classifiers = [
10
9
  "Development Status :: 3 - Alpha",
@@ -17,6 +16,7 @@ classifiers = [
17
16
  "Programming Language :: Python :: 3 :: Only",
18
17
  ]
19
18
  authors = [{ name = "Jacob Stewart", email = "jacob@swarmauri.com" }]
19
+
20
20
  dependencies = [
21
21
  "tigrbl-core",
22
22
  "tigrbl-base",
@@ -36,6 +36,14 @@ dependencies = [
36
36
  ]
37
37
 
38
38
 
39
+
40
+ [project.urls]
41
+ Organization = "https://github.com/tigrbl"
42
+ Discord = "https://discord.gg/K4YTAPapjR"
43
+ Homepage = "https://github.com/tigrbl/tigrbl"
44
+ Repository = "https://github.com/tigrbl/tigrbl"
45
+ Issues = "https://github.com/tigrbl/tigrbl/issues"
46
+
39
47
  [project.scripts]
40
48
  tigrbl = "tigrbl.cli:console_main"
41
49
 
@@ -101,6 +101,7 @@ from tigrbl_concrete._concrete import ( # noqa: E402
101
101
  PlainTextResponse,
102
102
  RedirectResponse,
103
103
  Request,
104
+ TransportResponse,
104
105
  UploadedFile,
105
106
  WebSocket,
106
107
  Response,
@@ -148,6 +149,8 @@ from tigrbl.ddl import bootstrap_dbschema, ensure_schemas, register_sqlite_attac
148
149
 
149
150
  from tigrbl_base._base import ( # noqa: E402
150
151
  AppBase,
152
+ EngineBase,
153
+ EngineProviderBase,
151
154
  ForeignKeyBase,
152
155
  HookBase,
153
156
  RouterBase,
@@ -161,6 +164,7 @@ from tigrbl_core._spec import ( # noqa: E402
161
164
  BindingSpec,
162
165
  ColumnSpec,
163
166
  EngineSpec,
167
+ EngineRegistry,
164
168
  Exchange,
165
169
  FieldSpec,
166
170
  Framing,
@@ -182,12 +186,18 @@ from tigrbl_core._spec import ( # noqa: E402
182
186
  SchemaRef,
183
187
  SchemaSpec,
184
188
  SessionSpec,
189
+ readonly,
190
+ session_spec,
185
191
  StorageSpec,
186
192
  StorageTransformSpec,
193
+ StorageTypeRef,
187
194
  TableRegistrySpec,
188
195
  TableSpec,
189
196
  TargetOp,
190
197
  TemplateSpec,
198
+ tx_read_committed,
199
+ tx_repeatable_read,
200
+ tx_serializable,
191
201
  TxScope,
192
202
  WebTransportBindingSpec,
193
203
  WsBindingSpec,
@@ -246,12 +256,47 @@ def include_tables(*args, **kwargs):
246
256
  )
247
257
 
248
258
 
259
+ def include_model(*args, **kwargs):
260
+ if len(args) == 1 and isinstance(args[0], type) and not kwargs:
261
+ return args[0], None
262
+ return import_module("tigrbl_concrete._mapping.router.include").include_model(
263
+ *args, **kwargs
264
+ )
265
+
266
+
267
+ def include_table(*args, **kwargs):
268
+ return import_module("tigrbl_concrete._mapping.router.include").include_table(
269
+ *args, **kwargs
270
+ )
271
+
272
+
273
+ def include_models(*args, **kwargs):
274
+ return import_module("tigrbl_concrete._mapping.router.include").include_models(
275
+ *args, **kwargs
276
+ )
277
+
278
+
249
279
  async def rpc_call(*args, **kwargs):
250
280
  return await import_module("tigrbl_concrete._mapping.router.rpc").rpc_call(
251
281
  *args, **kwargs
252
282
  )
253
283
 
254
284
 
285
+ from tigrbl_core._spec.session_spec import ( # noqa: E402
286
+ readonly as _readonly_fn,
287
+ session_spec as _session_spec_fn,
288
+ tx_read_committed as _tx_read_committed_fn,
289
+ tx_repeatable_read as _tx_repeatable_read_fn,
290
+ tx_serializable as _tx_serializable_fn,
291
+ )
292
+
293
+ readonly = _readonly_fn
294
+ session_spec = _session_spec_fn
295
+ tx_read_committed = _tx_read_committed_fn
296
+ tx_repeatable_read = _tx_repeatable_read_fn
297
+ tx_serializable = _tx_serializable_fn
298
+
299
+
255
300
  __all__ = [
256
301
  "specs",
257
302
  "base",
@@ -324,6 +369,9 @@ __all__ = [
324
369
  "build_handlers",
325
370
  "register_rpc",
326
371
  "build_rest",
372
+ "include_model",
373
+ "include_table",
374
+ "include_models",
327
375
  "include_tables",
328
376
  "rpc_call",
329
377
  "_invoke",
@@ -339,6 +387,7 @@ __all__ = [
339
387
  "UploadedFile",
340
388
  "WebSocket",
341
389
  "Response",
390
+ "TransportResponse",
342
391
  "JSONResponse",
343
392
  "BackgroundTask",
344
393
  "resolver",
@@ -355,11 +404,20 @@ __all__ = [
355
404
  "StorageTransform",
356
405
  "Middleware",
357
406
  "StorageTransformSpec",
407
+ "StorageTypeRef",
358
408
  "ForeignKeySpec",
359
409
  "RequestSpec",
360
410
  "SchemaSpec",
361
411
  "SessionSpec",
412
+ "session_spec",
413
+ "tx_read_committed",
414
+ "tx_repeatable_read",
415
+ "tx_serializable",
416
+ "readonly",
362
417
  "EngineSpec",
418
+ "EngineRegistry",
419
+ "EngineBase",
420
+ "EngineProviderBase",
363
421
  "TemplateSpec",
364
422
  "ForeignKeyBase",
365
423
  "HookBase",
@@ -0,0 +1,7 @@
1
+ """Compatibility export for core-owned canonical JSON helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from tigrbl_core.canonical_json import canonical_json_bytes, canonicalize
6
+
7
+ __all__ = ["canonical_json_bytes", "canonicalize"]
@@ -0,0 +1,8 @@
1
+ """Compatibility exports for legacy ``tigrbl.session`` imports."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from tigrbl_base._base import TigrblSessionBase
6
+ from tigrbl_core._spec.session_spec import SessionSpec
7
+
8
+ __all__ = ["SessionSpec", "TigrblSessionBase"]
@@ -0,0 +1,7 @@
1
+ """Compatibility module for ``tigrbl.session.base``."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from tigrbl_base._base import TigrblSessionBase
6
+
7
+ __all__ = ["TigrblSessionBase"]
@@ -0,0 +1,7 @@
1
+ """Compatibility module for ``tigrbl.session.spec``."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from tigrbl_core._spec.session_spec import SessionSpec
6
+
7
+ __all__ = ["SessionSpec"]
@@ -1,5 +1,9 @@
1
1
  from ...runtime.kernel import _default_kernel, build_phase_chains
2
2
  from .router import mount_diagnostics
3
+ from .healthz import build_healthz_endpoint, build_healthz_html, mount_healthz_uix
4
+ from .methodz import build_methodz_endpoint
5
+ from .hookz import build_hookz_endpoint
6
+ from .kernelz import build_kernelz_endpoint
3
7
  from .methodz import build_methodz_endpoint as _build_methodz_endpoint
4
8
  from .hookz import build_hookz_endpoint as _build_hookz_endpoint
5
9
  from .kernelz import build_kernelz_endpoint as _build_kernelz_endpoint
@@ -13,6 +17,12 @@ from .utils import (
13
17
 
14
18
  __all__ = [
15
19
  "mount_diagnostics",
20
+ "build_healthz_endpoint",
21
+ "build_healthz_html",
22
+ "mount_healthz_uix",
23
+ "build_methodz_endpoint",
24
+ "build_hookz_endpoint",
25
+ "build_kernelz_endpoint",
16
26
  "_build_methodz_endpoint",
17
27
  "_build_hookz_endpoint",
18
28
  "_build_kernelz_endpoint",
@@ -0,0 +1,285 @@
1
+ from __future__ import annotations
2
+
3
+ import inspect
4
+ import logging
5
+ from collections.abc import Iterable, Mapping
6
+ from typing import Any, Callable, Optional
7
+
8
+ from ... import Depends, Request
9
+ from ..._concrete import JSONResponse
10
+ from ..._concrete._response import Response
11
+ from .utils import maybe_execute
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ _NO_DB_WARNING = "db-not-configured"
16
+ _UNAVAILABLE_WARNING = "db-unavailable"
17
+
18
+
19
+ def _resolve_db(candidate: Any) -> Any:
20
+ """Resolve a DB-like object from either a DB handle or a Request object."""
21
+ if hasattr(candidate, "execute"):
22
+ return candidate
23
+
24
+ state = getattr(candidate, "state", None)
25
+ db = getattr(state, "db", None)
26
+ if db is not None:
27
+ return db
28
+ return None
29
+
30
+
31
+ def _has_execute(candidate: Any) -> bool:
32
+ return callable(getattr(candidate, "execute", None))
33
+
34
+
35
+ def _query_value(request: Any, name: str, default: str | None = None) -> str | None:
36
+ getter = getattr(request, "query_param", None)
37
+ if callable(getter):
38
+ return getter(name, default)
39
+ query = getattr(request, "query", None) or {}
40
+ values = query.get(name)
41
+ if isinstance(values, (list, tuple)) and values:
42
+ return str(values[0])
43
+ if values is not None:
44
+ return str(values)
45
+ return default
46
+
47
+
48
+ def _query_flag(request: Any, name: str) -> bool:
49
+ value = _query_value(request, name)
50
+ if value is None:
51
+ return False
52
+ return str(value).strip().lower() in {"1", "true", "yes", "on", "warn"}
53
+
54
+
55
+ def _wants_all_dbs(request: Any) -> bool:
56
+ value = _query_value(request, "dbs") or _query_value(request, "detail")
57
+ return str(value or "").strip().lower() in {"all", "full", "verbose"}
58
+
59
+
60
+ def _iter_db_entries(candidate: Any) -> list[tuple[str, Any]]:
61
+ if candidate is None:
62
+ return []
63
+ if _has_execute(candidate):
64
+ return [("default", candidate)]
65
+ if isinstance(candidate, Mapping):
66
+ return [
67
+ (str(name), db)
68
+ for name, db in candidate.items()
69
+ if _has_execute(db)
70
+ ]
71
+ if isinstance(candidate, Iterable) and not isinstance(
72
+ candidate, (str, bytes, bytearray)
73
+ ):
74
+ return [
75
+ (str(index), db)
76
+ for index, db in enumerate(candidate)
77
+ if _has_execute(db)
78
+ ]
79
+ return []
80
+
81
+
82
+ def _resolve_db_entries(candidate: Any, *sources: Any) -> list[tuple[str, Any]]:
83
+ entries = _iter_db_entries(candidate)
84
+ if entries:
85
+ return entries
86
+
87
+ state = getattr(candidate, "state", None)
88
+ app = getattr(candidate, "app", None)
89
+ for source in (state, app, *sources):
90
+ if source is None:
91
+ continue
92
+ for attr in ("dbs", "databases", "engines", "db"):
93
+ entries = _iter_db_entries(getattr(source, attr, None))
94
+ if entries:
95
+ return entries
96
+ return []
97
+
98
+
99
+ async def _check_db(name: str, db: Any) -> dict[str, Any]:
100
+ try:
101
+ await maybe_execute(db, "SELECT 1")
102
+ return {"name": name, "ok": True}
103
+ except Exception as exc: # pragma: no cover - covered by fixture fakes
104
+ logger.warning("/healthz degraded for %s: %s", name, exc)
105
+ return {
106
+ "name": name,
107
+ "ok": False,
108
+ "warning": _UNAVAILABLE_WARNING,
109
+ "error": str(exc),
110
+ }
111
+
112
+
113
+ def _multi_db_payload(results: list[dict[str, Any]], *, include_all: bool) -> dict[str, Any]:
114
+ total = len(results)
115
+ healthy = sum(1 for item in results if item["ok"])
116
+ payload: dict[str, Any] = {
117
+ "ok": healthy == total,
118
+ "dbs": {
119
+ "ok": healthy,
120
+ "total": total,
121
+ "failed": total - healthy,
122
+ },
123
+ }
124
+ if healthy != total:
125
+ payload["warning"] = _UNAVAILABLE_WARNING
126
+ if include_all:
127
+ payload["databases"] = results
128
+ return payload
129
+
130
+
131
+ def build_healthz_endpoint(
132
+ dep: Optional[Callable[..., Any]],
133
+ *,
134
+ router: Any | None = None,
135
+ warn_no_db: bool = False,
136
+ ):
137
+ """
138
+ Returns a ASGI endpoint function for /healthz.
139
+ If `dep` is provided, it's used as a dependency to supply `db`.
140
+ Otherwise, we try request.state.db.
141
+ """
142
+ if dep is not None:
143
+
144
+ async def _healthz(db: Any = Depends(dep)):
145
+ if hasattr(db, "dependency") and callable(getattr(db, "dependency", None)):
146
+ resolved = db.dependency()
147
+ if inspect.isawaitable(resolved):
148
+ resolved = await resolved
149
+ db = resolved
150
+ entries = _resolve_db_entries(db, router)
151
+ if not entries:
152
+ if warn_no_db:
153
+ return {"ok": True, "warning": _NO_DB_WARNING}
154
+ return {"ok": True}
155
+ if len(entries) == 1:
156
+ result = await _check_db(*entries[0])
157
+ if result["ok"]:
158
+ return {"ok": True}
159
+ return JSONResponse(
160
+ {
161
+ "ok": False,
162
+ "warning": result["warning"],
163
+ "error": result["error"],
164
+ },
165
+ status_code=200,
166
+ )
167
+ results = [await _check_db(name, db) for name, db in entries]
168
+ return _multi_db_payload(results, include_all=False)
169
+
170
+ return _healthz
171
+
172
+ async def _healthz(request: Request):
173
+ entries = _resolve_db_entries(request, router)
174
+ if not entries:
175
+ if warn_no_db or _query_flag(request, "warn_no_db"):
176
+ return {"ok": True, "warning": _NO_DB_WARNING}
177
+ return {"ok": True}
178
+ if len(entries) == 1:
179
+ result = await _check_db(*entries[0])
180
+ if result["ok"]:
181
+ return {"ok": True}
182
+ return JSONResponse(
183
+ {
184
+ "ok": False,
185
+ "warning": result["warning"],
186
+ "error": result["error"],
187
+ },
188
+ status_code=200,
189
+ )
190
+ results = [await _check_db(name, db) for name, db in entries]
191
+ return _multi_db_payload(results, include_all=_wants_all_dbs(request))
192
+
193
+ return _healthz
194
+
195
+
196
+ def _with_leading_slash(path: str) -> str:
197
+ return path if path.startswith("/") else f"/{path}"
198
+
199
+
200
+ def build_healthz_html(router: Any, request: Any, *, healthz_path: str | None = None) -> str:
201
+ """Return a small operator-facing HTML page for the JSON health endpoint."""
202
+
203
+ base = (getattr(request, "script_name", "") or "").rstrip("/")
204
+ configured = healthz_path or f"{getattr(router, 'system_prefix', '/system')}/healthz"
205
+ payload_url = f"{base}{_with_leading_slash(configured)}"
206
+ title = getattr(router, "title", "API")
207
+ return f"""<!doctype html>
208
+ <html>
209
+ <head>
210
+ <meta charset="utf-8" />
211
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
212
+ <title>{title} health</title>
213
+ <style>
214
+ body {{
215
+ margin: 0;
216
+ min-height: 100vh;
217
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
218
+ color: #111827;
219
+ background: #f8fafc;
220
+ }}
221
+ main {{
222
+ width: min(720px, calc(100% - 32px));
223
+ margin: 0 auto;
224
+ padding: 48px 0;
225
+ }}
226
+ h1 {{
227
+ font-size: 24px;
228
+ font-weight: 650;
229
+ margin: 0 0 16px;
230
+ }}
231
+ pre {{
232
+ overflow: auto;
233
+ padding: 16px;
234
+ background: #111827;
235
+ color: #e5e7eb;
236
+ border-radius: 6px;
237
+ }}
238
+ </style>
239
+ </head>
240
+ <body>
241
+ <main>
242
+ <h1>Health</h1>
243
+ <pre id="payload">Loading {payload_url}</pre>
244
+ </main>
245
+ <script>
246
+ const payload = document.getElementById("payload");
247
+ fetch("{payload_url}", {{ headers: {{ "accept": "application/json" }} }})
248
+ .then((response) => response.json())
249
+ .then((data) => {{
250
+ payload.textContent = JSON.stringify(data, null, 2);
251
+ payload.dataset.ok = String(data.ok === true);
252
+ }})
253
+ .catch((error) => {{
254
+ payload.textContent = JSON.stringify({{ ok: false, error: String(error) }}, null, 2);
255
+ payload.dataset.ok = "false";
256
+ }});
257
+ </script>
258
+ </body>
259
+ </html>
260
+ """
261
+
262
+
263
+ def mount_healthz_uix(
264
+ router: Any,
265
+ *,
266
+ path: str = "/healthz",
267
+ name: str = "__healthz_uix__",
268
+ healthz_path: str | None = None,
269
+ ) -> Any:
270
+ """Mount a human-viewable health page that reads the JSON health endpoint."""
271
+
272
+ def _healthz_uix_handler(request: Any) -> Response:
273
+ return Response.html(
274
+ build_healthz_html(router, request, healthz_path=healthz_path)
275
+ )
276
+
277
+ router.add_route(
278
+ path,
279
+ _healthz_uix_handler,
280
+ methods=["GET"],
281
+ name=name,
282
+ include_in_schema=False,
283
+ inherit_owner_dependencies=False,
284
+ )
285
+ return router
@@ -8,16 +8,6 @@ from tigrbl_concrete.decorators import allow_anon
8
8
  from tigrbl_typing import types as _typing_types
9
9
  from tigrbl_typing.types import __all__ as _typing_all
10
10
 
11
- _DEPRECATED_NAMES = {
12
- "Router": "tigrbl",
13
- "Request": "tigrbl",
14
- "Body": "tigrbl.core.crud",
15
- "Depends": "tigrbl.security",
16
- "HTTPException": "tigrbl.runtime.status",
17
- "Response": "tigrbl",
18
- }
19
-
20
-
21
11
  for _name in _typing_all:
22
12
  globals()[_name] = getattr(_typing_types, _name)
23
13
 
@@ -26,11 +16,5 @@ __all__ = [*_typing_all, "F", "IO", "S", "acol", "allow_anon"]
26
16
 
27
17
 
28
18
  def __getattr__(name: str):
29
- if name in _DEPRECATED_NAMES:
30
- module = _DEPRECATED_NAMES[name]
31
- raise AttributeError(
32
- f"tigrbl.types no longer exports '{name}'. "
33
- f"Import it from '{module}' instead."
34
- )
35
19
  raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
36
20
 
@@ -1,69 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import inspect
4
- import logging
5
- from typing import Any, Callable, Optional
6
-
7
- from ... import Depends, Request
8
- from ..._concrete import JSONResponse
9
- from .utils import maybe_execute
10
-
11
- logger = logging.getLogger(__name__)
12
-
13
-
14
- def _resolve_db(candidate: Any) -> Any:
15
- """Resolve a DB-like object from either a DB handle or a Request object."""
16
- if hasattr(candidate, "execute"):
17
- return candidate
18
-
19
- state = getattr(candidate, "state", None)
20
- db = getattr(state, "db", None)
21
- if db is not None:
22
- return db
23
- return None
24
-
25
-
26
- def build_healthz_endpoint(dep: Optional[Callable[..., Any]]):
27
- """
28
- Returns a ASGI endpoint function for /healthz.
29
- If `dep` is provided, it's used as a dependency to supply `db`.
30
- Otherwise, we try request.state.db.
31
- """
32
- if dep is not None:
33
-
34
- async def _healthz(db: Any = Depends(dep)):
35
- if hasattr(db, "dependency") and callable(getattr(db, "dependency", None)):
36
- resolved = db.dependency()
37
- if inspect.isawaitable(resolved):
38
- resolved = await resolved
39
- db = resolved
40
- db = _resolve_db(db)
41
- if db is None:
42
- return {"ok": True, "warning": "no-db"}
43
- try:
44
- await maybe_execute(db, "SELECT 1")
45
- return {"ok": True}
46
- except Exception as e: # pragma: no cover
47
- logger.warning("/healthz degraded: %s", e)
48
- return JSONResponse(
49
- {"ok": False, "warning": "db-unavailable", "error": str(e)},
50
- status_code=200,
51
- )
52
-
53
- return _healthz
54
-
55
- async def _healthz(request: Request):
56
- db = _resolve_db(request)
57
- if db is None:
58
- return {"ok": True, "warning": "no-db"}
59
- try:
60
- await maybe_execute(db, "SELECT 1")
61
- return {"ok": True}
62
- except Exception as e: # pragma: no cover
63
- logger.warning("/healthz degraded: %s", e)
64
- return JSONResponse(
65
- {"ok": False, "warning": "db-unavailable", "error": str(e)},
66
- status_code=200,
67
- )
68
-
69
- return _healthz
File without changes
File without changes