vortexpy 4.0.29__tar.gz → 4.1.2__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 (110) hide show
  1. {vortexpy-4.0.29 → vortexpy-4.1.2}/PKG-INFO +1 -1
  2. {vortexpy-4.0.29 → vortexpy-4.1.2}/pyproject.toml +1 -1
  3. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/Tuple.py +17 -0
  4. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/TupleRestful.py +32 -1
  5. vortexpy-4.1.2/vortex/__init__.py +9 -0
  6. vortexpy-4.1.2/vortex/tuple_json_schema/__init__.py +18 -0
  7. vortexpy-4.1.2/vortex/tuple_json_schema/constants.py +35 -0
  8. vortexpy-4.1.2/vortex/tuple_json_schema/json_schema_node_tuple.py +99 -0
  9. vortexpy-4.1.2/vortex/tuple_json_schema/tuple_json_schema_builder.py +225 -0
  10. vortexpy-4.1.2/vortex/tuple_json_schema/tuple_json_schema_test.py +257 -0
  11. vortexpy-4.1.2/vortex/tuple_json_schema/tuple_json_schema_test_tuples.py +69 -0
  12. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortexpy.egg-info/PKG-INFO +1 -1
  13. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortexpy.egg-info/SOURCES.txt +6 -0
  14. vortexpy-4.1.2/vortexpy.egg-info/dependency_links.txt +1 -0
  15. vortexpy-4.0.29/vortex/__init__.py +0 -9
  16. vortexpy-4.0.29/vortex/py.typed +0 -0
  17. {vortexpy-4.0.29 → vortexpy-4.1.2}/README.md +0 -0
  18. {vortexpy-4.0.29 → vortexpy-4.1.2}/setup.cfg +0 -0
  19. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/DataWrapTuple.py +0 -0
  20. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/DeferUtil.py +0 -0
  21. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/Jsonable.py +0 -0
  22. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/Payload.py +0 -0
  23. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/PayloadEndpoint.py +0 -0
  24. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/PayloadEndpointTest.py +0 -0
  25. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/PayloadEnvelope.py +0 -0
  26. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/PayloadFilterKeys.py +0 -0
  27. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/PayloadIO.py +0 -0
  28. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/PayloadPriority.py +0 -0
  29. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/PayloadResponse.py +0 -0
  30. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/PayloadResponseTest.py +0 -0
  31. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/PayloadTest.py +0 -0
  32. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/PerformTestActionTuple.py +0 -0
  33. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/SerialiseUtil.py +0 -0
  34. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/TestTuple.py +0 -0
  35. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/TupleAction.py +0 -0
  36. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/TupleDataForTest.py +0 -0
  37. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/TuplePerfTest.py +0 -0
  38. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/TuplePolymorphicTupleTypeFieldArgTest.py +0 -0
  39. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/TupleRestfulTest.py +0 -0
  40. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/TupleSelector.py +0 -0
  41. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/TupleTest.py +0 -0
  42. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/VortexABC.py +0 -0
  43. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/VortexAccessControlContextABC.py +0 -0
  44. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/VortexAuthenticatorABC.py +0 -0
  45. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/VortexClientHttp.py +0 -0
  46. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/VortexClientHttpTest.py +0 -0
  47. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/VortexClientTcp.py +0 -0
  48. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/VortexClientWebsocketFactory.py +0 -0
  49. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/VortexConnectionABC.py +0 -0
  50. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/VortexEndpointPermission.py +0 -0
  51. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/VortexFactory.py +0 -0
  52. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/VortexPayloadProtocol.py +0 -0
  53. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/VortexPermission.py +0 -0
  54. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/VortexPermissionError.py +0 -0
  55. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/VortexServer.py +0 -0
  56. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/VortexServerConnection.py +0 -0
  57. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/VortexServerHttpResource.py +0 -0
  58. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/VortexServerTcp.py +0 -0
  59. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/VortexServerWebsocket.py +0 -0
  60. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/VortexSession.py +0 -0
  61. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/VortexTcpMemoryLeakPayloadTest.py +0 -0
  62. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/VortexTcpMemoryLeakRawTest.py +0 -0
  63. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/VortexTest.py +0 -0
  64. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/VortexUtil.py +0 -0
  65. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/VortexWritePushProducer.py +0 -0
  66. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/data_loader/TupleDataLiveLockManager.py +0 -0
  67. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/data_loader/TupleDataLoader.py +0 -0
  68. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/data_loader/TupleDataLoaderDelegate.py +0 -0
  69. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/data_loader/TupleDataLoaderTupleABC.py +0 -0
  70. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/data_loader/TupleDataLoaderTuples.py +0 -0
  71. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/data_loader/__init__.py +0 -0
  72. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/event_bus/TupleChangeEventABC.py +0 -0
  73. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/event_bus/TupleChangeEventBus.py +0 -0
  74. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/event_bus/TupleChangeEventBusObserverABC.py +0 -0
  75. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/event_bus/__init__.py +0 -0
  76. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/handler/TupleActionProcessor.py +0 -0
  77. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/handler/TupleActionProcessorProxy.py +0 -0
  78. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/handler/TupleActionProcessorTestHandler.py +0 -0
  79. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/handler/TupleDataAccessGranterABC.py +0 -0
  80. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/handler/TupleDataActionClient.py +0 -0
  81. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/handler/TupleDataObservableCache.py +0 -0
  82. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/handler/TupleDataObservableHandler.py +0 -0
  83. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/handler/TupleDataObservableProxyHandler.py +0 -0
  84. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/handler/TupleDataObservableTestHandler.py +0 -0
  85. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/handler/TupleDataObserverClient.py +0 -0
  86. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/handler/TupleDataProviderABC.py +0 -0
  87. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/handler/VortexJSTupleLoaderTestHandler.py +0 -0
  88. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/handler/__init__.py +0 -0
  89. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/mypy_plugin.py +0 -0
  90. /vortexpy-4.0.29/vortexpy.egg-info/dependency_links.txt → /vortexpy-4.1.2/vortex/py.typed +0 -0
  91. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/restful/GzippedDataHttpClient.py +0 -0
  92. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/restful/GzippedDataHttpClientTest.py +0 -0
  93. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/restful/GzippedPayloadHttpClient.py +0 -0
  94. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/restful/RestfulHttpClient.py +0 -0
  95. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/restful/RestfulHttpClientTest.py +0 -0
  96. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/restful/RestfulResource.py +0 -0
  97. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/restful/RestfulResourceTest.py +0 -0
  98. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/restful/__init__.py +0 -0
  99. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/rpc/RPC.py +0 -0
  100. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/rpc/RPCTest.py +0 -0
  101. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/rpc/__init__.py +0 -0
  102. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/sqla_orm/OrmCrudHandler.py +0 -0
  103. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/sqla_orm/TupleProviderForDb.py +0 -0
  104. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/sqla_orm/VortexTupleJson.py +0 -0
  105. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/sqla_orm/__init__.py +0 -0
  106. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/storage/TupleStorageSqlite.py +0 -0
  107. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortex/storage/__init__.py +0 -0
  108. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortexpy.egg-info/entry_points.txt +0 -0
  109. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortexpy.egg-info/requires.txt +0 -0
  110. {vortexpy-4.0.29 → vortexpy-4.1.2}/vortexpy.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vortexpy
3
- Version: 4.0.29
3
+ Version: 4.1.2
4
4
  Summary: Flying Ion's observable, routable, data serialisation and transport code.
5
5
  Author-email: Flying Ion Pty Ltd <contact@flyingion.com>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "vortexpy"
7
- version = "4.0.29"
7
+ version = "4.1.2"
8
8
  license = {text = "MIT"}
9
9
  authors = [{name = "Flying Ion Pty Ltd", email = "contact@flyingion.com"}]
10
10
  description = "Flying Ion's observable, routable, data serialisation and transport code."
@@ -595,6 +595,23 @@ class Tuple(Jsonable, _TupleToPlainJsonMixin, _TupleToSqlaJsonMixin):
595
595
  def tupleFieldNames(cls) -> List[str]:
596
596
  return cls.__fieldNames__
597
597
 
598
+ @classmethod
599
+ def tupleJsonSchema(cls) -> "JsonSchemaNodeTuple":
600
+ """Tuple JSON Schema
601
+
602
+ Build a typed Draft 2020-12 subset schema tree from this tuple's
603
+ ``TupleField`` metadata.
604
+
605
+ :return: Root ``JsonSchemaNodeTuple``. Call ``buildJsonSchemaDict()``
606
+ for Draft 2020-12 JSON Schema keys, or ``tupleToRestfulJsonDict()``
607
+ for Vortex restful JSON.
608
+ """
609
+ from vortex.tuple_json_schema.tuple_json_schema_builder import (
610
+ TupleJsonSchemaBuilder,
611
+ )
612
+
613
+ return TupleJsonSchemaBuilder.buildJsonSchema(cls)
614
+
598
615
  @classmethod
599
616
  def tupleName(cls): # DEPRECIATED
600
617
  return cls.__tupleType__
@@ -19,7 +19,7 @@ from dataclasses import dataclass, field
19
19
  from datetime import datetime
20
20
  from typing import TYPE_CHECKING, Annotated, Any, NamedTuple, Optional, Type
21
21
 
22
- from typing import get_args
22
+ from typing import Union, get_args
23
23
  from typing import get_origin
24
24
  from typing import get_type_hints
25
25
 
@@ -256,6 +256,24 @@ class TupleRestfulConverter:
256
256
 
257
257
  return True
258
258
 
259
+ @classmethod
260
+ def _nestedTupleTypeFromHint(cls, typeHint: Any) -> Any | None:
261
+ """Return nested tuple class for a field hint, including ``X | None`` unions."""
262
+ if hasattr(typeHint, "__tupleType__"):
263
+ return typeHint
264
+
265
+ unionArgs = get_args(typeHint)
266
+ if not unionArgs:
267
+ return None
268
+
269
+ for unionArg in unionArgs:
270
+ if unionArg is type(None):
271
+ continue
272
+ if hasattr(unionArg, "__tupleType__"):
273
+ return unionArg
274
+
275
+ return None
276
+
259
277
  @classmethod
260
278
  def _checkType(
261
279
  cls,
@@ -284,6 +302,9 @@ class TupleRestfulConverter:
284
302
  if hasattr(typeHint, "__tupleType__"):
285
303
  return None
286
304
 
305
+ if cls._nestedTupleTypeFromHint(typeHint) is not None:
306
+ return None
307
+
287
308
  if type(value) in get_args(typeHint):
288
309
  if validator:
289
310
  try:
@@ -425,6 +446,16 @@ class TupleRestfulConverter:
425
446
  cls._prefixValidationErrors(child.errors, field_path),
426
447
  )
427
448
 
449
+ nestedTupleType = cls._nestedTupleTypeFromHint(typeHint)
450
+ if nestedTupleType is not None and nestedTupleType is not typeHint:
451
+ child = cls.restfulJsonDictToTupleWithValidationResult(
452
+ value, nestedTupleType
453
+ )
454
+ return _ConvertResult(
455
+ child.tuple_,
456
+ cls._prefixValidationErrors(child.errors, field_path),
457
+ )
458
+
428
459
  if not get_args(typeHint):
429
460
  return _ConvertResult(
430
461
  _UNSET,
@@ -0,0 +1,9 @@
1
+ # Copyright (c) 2016-2026 Flying Ion Pty Ltd
2
+ #
3
+ # This software is open source; the MIT license applies.
4
+ # See LICENSE for the full license text.
5
+ #
6
+ # Website : https://flyingion.com
7
+ # Support : contact@flyingion.com
8
+
9
+ __version__ = '4.1.2'
@@ -0,0 +1,18 @@
1
+ # Copyright (c) 2016-2026 Flying Ion Pty Ltd
2
+ #
3
+ # This software is open source; the MIT license applies.
4
+ # See LICENSE for the full license text.
5
+ #
6
+ # Website : https://flyingion.com
7
+ # Support : contact@flyingion.com
8
+
9
+ """Tuple JSON Schema — typed Vortex tuples for Draft 2020-12 subset schemas.
10
+
11
+ Import from submodules::
12
+
13
+ from vortex.tuple_json_schema.json_schema_node_tuple import JsonSchemaNodeTuple
14
+ from vortex.tuple_json_schema.tuple_json_schema_builder import (
15
+ TupleJsonSchemaBuilder,
16
+ )
17
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_TYPE_STRING
18
+ """
@@ -0,0 +1,35 @@
1
+ # Copyright (c) 2016-2026 Flying Ion Pty Ltd
2
+ #
3
+ # This software is open source; the MIT license applies.
4
+ # See LICENSE for the full license text.
5
+ #
6
+ # Website : https://flyingion.com
7
+ # Support : contact@flyingion.com
8
+
9
+ """JSON Schema string constants for Draft 2020-12 subset."""
10
+
11
+ JSON_SCHEMA_DRAFT_2020_12 = "https://json-schema.org/draft/2020-12/schema"
12
+
13
+ JSON_SCHEMA_KEY_SCHEMA = "$schema"
14
+ JSON_SCHEMA_KEY_DEFS = "$defs"
15
+ JSON_SCHEMA_KEY_REF = "$ref"
16
+ JSON_SCHEMA_KEY_TYPE = "type"
17
+ JSON_SCHEMA_KEY_TITLE = "title"
18
+ JSON_SCHEMA_KEY_PROPERTIES = "properties"
19
+ JSON_SCHEMA_KEY_REQUIRED = "required"
20
+ JSON_SCHEMA_KEY_ITEMS = "items"
21
+ JSON_SCHEMA_KEY_FORMAT = "format"
22
+ JSON_SCHEMA_KEY_CONTENT_ENCODING = "contentEncoding"
23
+ JSON_SCHEMA_KEY_DESCRIPTION = "description"
24
+
25
+ JSON_SCHEMA_TYPE_OBJECT = "object"
26
+ JSON_SCHEMA_TYPE_STRING = "string"
27
+ JSON_SCHEMA_TYPE_INTEGER = "integer"
28
+ JSON_SCHEMA_TYPE_NUMBER = "number"
29
+ JSON_SCHEMA_TYPE_BOOLEAN = "boolean"
30
+ JSON_SCHEMA_TYPE_ARRAY = "array"
31
+
32
+ JSON_SCHEMA_FORMAT_DATE_TIME = "date-time"
33
+ JSON_SCHEMA_CONTENT_ENCODING_BINARY = "binary"
34
+
35
+ JSON_SCHEMA_DEFS_REF_PREFIX = f"#/{JSON_SCHEMA_KEY_DEFS}/"
@@ -0,0 +1,99 @@
1
+ # Copyright (c) 2016-2026 Flying Ion Pty Ltd
2
+ #
3
+ # This software is open source; the MIT license applies.
4
+ # See LICENSE for the full license text.
5
+ #
6
+ # Website : https://flyingion.com
7
+ # Support : contact@flyingion.com
8
+
9
+ """JSON Schema node as a Vortex tuple (Draft 2020-12 subset)."""
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import Any
14
+
15
+ from vortex.Tuple import Tuple
16
+ from vortex.Tuple import TupleField
17
+ from vortex.Tuple import addTupleType
18
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_KEY_CONTENT_ENCODING
19
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_KEY_DEFS
20
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_KEY_DESCRIPTION
21
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_KEY_FORMAT
22
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_KEY_ITEMS
23
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_KEY_PROPERTIES
24
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_KEY_REF
25
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_KEY_REQUIRED
26
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_KEY_SCHEMA
27
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_KEY_TITLE
28
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_KEY_TYPE
29
+
30
+
31
+ @addTupleType
32
+ class JsonSchemaNodeTuple(Tuple):
33
+ """JSON Schema Node Tuple
34
+
35
+ Typed Draft 2020-12 subset node. Application code uses this tuple;
36
+ call ``buildJsonSchemaDict()`` only at a boundary that needs raw JSON Schema
37
+ (``$schema`` / ``$ref`` / ``$defs`` keys). Prefer
38
+ ``tupleToRestfulJsonDict()`` when serialising as a normal Vortex tuple.
39
+ """
40
+
41
+ __tupleType__ = "vortex.JsonSchemaNodeTuple"
42
+
43
+ schemaUri: str | None = TupleField(None, typingType=str | None)
44
+ schemaType: str | None = TupleField(None, typingType=str | None)
45
+ title: str | None = TupleField(None, typingType=str | None)
46
+ description: str | None = TupleField(None, typingType=str | None)
47
+ format: str | None = TupleField(None, typingType=str | None)
48
+ contentEncoding: str | None = TupleField(None, typingType=str | None)
49
+ ref: str | None = TupleField(None, typingType=str | None)
50
+ items: JsonSchemaNodeTuple | None = TupleField(None)
51
+ properties: dict[str, JsonSchemaNodeTuple] | None = TupleField(None)
52
+ required: list[str] | None = TupleField(None, typingType=list[str] | None)
53
+ defs: dict[str, JsonSchemaNodeTuple] | None = TupleField(None)
54
+
55
+ def buildJsonSchemaDict(self) -> dict[str, Any]:
56
+ """Build JSON Schema Dict
57
+
58
+ Emit a Draft 2020-12 subset dict with JSON Schema keys
59
+ (``$schema``, ``$ref``, ``$defs``, ``type``, …).
60
+
61
+ :return: JSON-serialisable schema dict.
62
+ """
63
+ out: dict[str, Any] = {}
64
+ if self.schemaUri is not None:
65
+ out[JSON_SCHEMA_KEY_SCHEMA] = self.schemaUri
66
+ if self.schemaType is not None:
67
+ out[JSON_SCHEMA_KEY_TYPE] = self.schemaType
68
+ if self.title is not None:
69
+ out[JSON_SCHEMA_KEY_TITLE] = self.title
70
+ if self.description is not None:
71
+ out[JSON_SCHEMA_KEY_DESCRIPTION] = self.description
72
+ if self.format is not None:
73
+ out[JSON_SCHEMA_KEY_FORMAT] = self.format
74
+ if self.contentEncoding is not None:
75
+ out[JSON_SCHEMA_KEY_CONTENT_ENCODING] = self.contentEncoding
76
+ if self.ref is not None:
77
+ out[JSON_SCHEMA_KEY_REF] = self.ref
78
+ if self.items is not None:
79
+ out[JSON_SCHEMA_KEY_ITEMS] = self.items.buildJsonSchemaDict()
80
+ if self.properties is not None:
81
+ out[JSON_SCHEMA_KEY_PROPERTIES] = {
82
+ name: node.buildJsonSchemaDict()
83
+ for name, node in self.properties.items()
84
+ }
85
+ if self.required is not None:
86
+ out[JSON_SCHEMA_KEY_REQUIRED] = list(self.required)
87
+ if self.defs is not None:
88
+ out[JSON_SCHEMA_KEY_DEFS] = {
89
+ name: node.buildJsonSchemaDict()
90
+ for name, node in self.defs.items()
91
+ }
92
+ return out
93
+
94
+
95
+ JsonSchemaNodeTuple.items.typingType = JsonSchemaNodeTuple | None
96
+ JsonSchemaNodeTuple.properties.typingType = (
97
+ dict[str, JsonSchemaNodeTuple] | None
98
+ )
99
+ JsonSchemaNodeTuple.defs.typingType = dict[str, JsonSchemaNodeTuple] | None
@@ -0,0 +1,225 @@
1
+ # Copyright (c) 2016-2026 Flying Ion Pty Ltd
2
+ #
3
+ # This software is open source; the MIT license applies.
4
+ # See LICENSE for the full license text.
5
+ #
6
+ # Website : https://flyingion.com
7
+ # Support : contact@flyingion.com
8
+
9
+ """Build a ``JsonSchemaNodeTuple`` tree from ``TupleField`` metadata."""
10
+
11
+ from __future__ import annotations
12
+
13
+ import types
14
+ from datetime import datetime
15
+ from typing import Any
16
+ from typing import Dict
17
+ from typing import List
18
+ from typing import Union
19
+ from typing import get_args
20
+ from typing import get_origin
21
+
22
+ from vortex.Tuple import Tuple
23
+ from vortex.Tuple import TupleField
24
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_CONTENT_ENCODING_BINARY
25
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_DEFS_REF_PREFIX
26
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_DRAFT_2020_12
27
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_FORMAT_DATE_TIME
28
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_TYPE_ARRAY
29
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_TYPE_BOOLEAN
30
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_TYPE_INTEGER
31
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_TYPE_NUMBER
32
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_TYPE_OBJECT
33
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_TYPE_STRING
34
+ from vortex.tuple_json_schema.json_schema_node_tuple import JsonSchemaNodeTuple
35
+
36
+
37
+ class TupleJsonSchemaBuilder:
38
+ """Tuple JSON Schema Builder
39
+
40
+ Builds a ``JsonSchemaNodeTuple`` tree from a ``Tuple`` subclass using
41
+ ``TupleField.typingType``, ``defaultValue``, and ``comment``.
42
+ """
43
+
44
+ @classmethod
45
+ def buildJsonSchema(cls, tupleCls: type[Tuple]) -> JsonSchemaNodeTuple:
46
+ """Build JSON Schema Node
47
+
48
+ Produce a typed schema tree for ``tupleCls`` from its ``TupleField``
49
+ entries.
50
+
51
+ :param tupleCls: Registered ``Tuple`` subclass to describe.
52
+ :return: Root ``JsonSchemaNodeTuple`` (call ``buildJsonSchemaDict()``
53
+ for Draft 2020-12 JSON Schema keys).
54
+ """
55
+ builder = cls()
56
+ return builder._buildRoot(tupleCls)
57
+
58
+ def __init__(self) -> None:
59
+ self._defs: dict[str, JsonSchemaNodeTuple] = {}
60
+ self._building: set[str] = set()
61
+
62
+ def _buildRoot(self, tupleCls: type[Tuple]) -> JsonSchemaNodeTuple:
63
+ # Build object schema for the root tuple type
64
+ objectSchema = self._buildObjectSchema(tupleCls)
65
+ rootTupleType = tupleCls.tupleType()
66
+
67
+ # Self-references emit $ref into defs; place the root object schema there
68
+ # (without draft URI or nested defs).
69
+ if rootTupleType in self._defs:
70
+ defsCopy = objectSchema.tupleClone(deep=True)
71
+ defsCopy.schemaUri = None
72
+ defsCopy.defs = None
73
+ self._defs[rootTupleType] = defsCopy
74
+
75
+ # Assemble root document with draft URI and optional defs
76
+ objectSchema.schemaUri = JSON_SCHEMA_DRAFT_2020_12
77
+ if self._defs:
78
+ objectSchema.defs = dict(self._defs)
79
+
80
+ return objectSchema
81
+
82
+ def _buildObjectSchema(self, tupleCls: type[Tuple]) -> JsonSchemaNodeTuple:
83
+ tupleTypeName = tupleCls.tupleType()
84
+ self._building.add(tupleTypeName)
85
+
86
+ # Map each TupleField to a property schema and required list
87
+ properties: dict[str, JsonSchemaNodeTuple] = {}
88
+ required: list[str] = []
89
+ for fieldName in tupleCls.tupleFieldNames():
90
+ field = getattr(tupleCls, fieldName, None)
91
+ if not isinstance(field, TupleField):
92
+ continue
93
+
94
+ propSchema = self._schemaForTypingType(field.typingType)
95
+ if field.comment:
96
+ propSchema.description = field.comment
97
+
98
+ properties[fieldName] = propSchema
99
+ if not self._isOptionalField(field):
100
+ required.append(fieldName)
101
+
102
+ self._building.discard(tupleTypeName)
103
+
104
+ # Assemble object schema
105
+ objectSchema = JsonSchemaNodeTuple(
106
+ schemaType=JSON_SCHEMA_TYPE_OBJECT,
107
+ title=tupleTypeName,
108
+ properties=properties,
109
+ )
110
+ if required:
111
+ objectSchema.required = required
112
+
113
+ return objectSchema
114
+
115
+ def _isOptionalField(self, field: TupleField) -> bool:
116
+ if field.defaultValue is None:
117
+ return True
118
+ return self._typingIncludesNone(field.typingType)
119
+
120
+ def _typingIncludesNone(self, typingType: Any) -> bool:
121
+ if typingType is None:
122
+ return False
123
+
124
+ origin = get_origin(typingType)
125
+ if origin is Union or origin is types.UnionType:
126
+ return type(None) in get_args(typingType)
127
+ return False
128
+
129
+ def _unwrapOptional(self, typingType: Any) -> Any:
130
+ origin = get_origin(typingType)
131
+ if origin is not Union and origin is not types.UnionType:
132
+ return typingType
133
+
134
+ nonNoneArgs = [
135
+ arg for arg in get_args(typingType) if arg is not type(None)
136
+ ]
137
+ if len(nonNoneArgs) == 1:
138
+ return nonNoneArgs[0]
139
+
140
+ if not nonNoneArgs:
141
+ return typingType
142
+
143
+ raise NotImplementedError(
144
+ f"Unsupported multi-member Union for JSON Schema: {typingType!r}"
145
+ )
146
+
147
+ def _schemaForTypingType(self, typingType: Any) -> JsonSchemaNodeTuple:
148
+ # Missing typingType maps to string (TupleField default)
149
+ if typingType is None:
150
+ return JsonSchemaNodeTuple(schemaType=JSON_SCHEMA_TYPE_STRING)
151
+
152
+ # Scalar and built-in types
153
+ typingType = self._unwrapOptional(typingType)
154
+ if typingType is str:
155
+ return JsonSchemaNodeTuple(schemaType=JSON_SCHEMA_TYPE_STRING)
156
+
157
+ if typingType is int:
158
+ return JsonSchemaNodeTuple(schemaType=JSON_SCHEMA_TYPE_INTEGER)
159
+
160
+ if typingType is float:
161
+ return JsonSchemaNodeTuple(schemaType=JSON_SCHEMA_TYPE_NUMBER)
162
+
163
+ if typingType is bool:
164
+ return JsonSchemaNodeTuple(schemaType=JSON_SCHEMA_TYPE_BOOLEAN)
165
+
166
+ if typingType is datetime:
167
+ return JsonSchemaNodeTuple(
168
+ schemaType=JSON_SCHEMA_TYPE_STRING,
169
+ format=JSON_SCHEMA_FORMAT_DATE_TIME,
170
+ )
171
+
172
+ if typingType in (bytes, bytearray):
173
+ return JsonSchemaNodeTuple(
174
+ schemaType=JSON_SCHEMA_TYPE_STRING,
175
+ contentEncoding=JSON_SCHEMA_CONTENT_ENCODING_BINARY,
176
+ )
177
+
178
+ # Collections
179
+ origin = get_origin(typingType)
180
+ if origin in (list, List) or typingType is list:
181
+ args = get_args(typingType)
182
+ itemType = args[0] if args else None
183
+ return JsonSchemaNodeTuple(
184
+ schemaType=JSON_SCHEMA_TYPE_ARRAY,
185
+ items=self._schemaForTypingType(itemType),
186
+ )
187
+
188
+ if origin in (dict, Dict) or typingType is dict:
189
+ return JsonSchemaNodeTuple(schemaType=JSON_SCHEMA_TYPE_OBJECT)
190
+
191
+ # Nested Tuple subclasses
192
+ if self._isTupleSubclass(typingType):
193
+ return self._refForTuple(typingType)
194
+
195
+ raise NotImplementedError(
196
+ f"Unsupported TupleField typingType for JSON Schema: {typingType!r}"
197
+ )
198
+
199
+ def _isTupleSubclass(self, typingType: Any) -> bool:
200
+ return (
201
+ isinstance(typingType, type)
202
+ and issubclass(typingType, Tuple)
203
+ and typingType is not Tuple
204
+ )
205
+
206
+ def _refForTuple(self, tupleCls: type[Tuple]) -> JsonSchemaNodeTuple:
207
+ tupleTypeName = tupleCls.tupleType()
208
+ ref = JsonSchemaNodeTuple(
209
+ ref=f"{JSON_SCHEMA_DEFS_REF_PREFIX}{tupleTypeName}"
210
+ )
211
+
212
+ # Cycle: emit $ref only; outer build places the object in defs
213
+ if tupleTypeName in self._building:
214
+ if tupleTypeName not in self._defs:
215
+ self._defs[tupleTypeName] = JsonSchemaNodeTuple(
216
+ schemaType=JSON_SCHEMA_TYPE_OBJECT,
217
+ title=tupleTypeName,
218
+ properties={},
219
+ )
220
+ return ref
221
+
222
+ if tupleTypeName not in self._defs:
223
+ self._defs[tupleTypeName] = self._buildObjectSchema(tupleCls)
224
+
225
+ return ref
@@ -0,0 +1,257 @@
1
+ # Copyright (c) 2016-2026 Flying Ion Pty Ltd
2
+ #
3
+ # This software is open source; the MIT license applies.
4
+ # See LICENSE for the full license text.
5
+ #
6
+ # Website : https://flyingion.com
7
+ # Support : contact@flyingion.com
8
+
9
+ from twisted.trial import unittest
10
+
11
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_CONTENT_ENCODING_BINARY
12
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_DEFS_REF_PREFIX
13
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_DRAFT_2020_12
14
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_FORMAT_DATE_TIME
15
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_KEY_DEFS
16
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_KEY_DESCRIPTION
17
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_KEY_FORMAT
18
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_KEY_ITEMS
19
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_KEY_PROPERTIES
20
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_KEY_REF
21
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_KEY_REQUIRED
22
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_KEY_SCHEMA
23
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_KEY_TITLE
24
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_KEY_TYPE
25
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_TYPE_ARRAY
26
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_TYPE_BOOLEAN
27
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_TYPE_INTEGER
28
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_TYPE_NUMBER
29
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_TYPE_OBJECT
30
+ from vortex.tuple_json_schema.constants import JSON_SCHEMA_TYPE_STRING
31
+ from vortex.tuple_json_schema.json_schema_node_tuple import JsonSchemaNodeTuple
32
+ from vortex.tuple_json_schema.tuple_json_schema_test_tuples import (
33
+ JsonSchemaBareListTuple,
34
+ )
35
+ from vortex.tuple_json_schema.tuple_json_schema_test_tuples import (
36
+ JsonSchemaNestedTuple,
37
+ )
38
+ from vortex.tuple_json_schema.tuple_json_schema_test_tuples import (
39
+ JsonSchemaParentTuple,
40
+ )
41
+ from vortex.tuple_json_schema.tuple_json_schema_test_tuples import (
42
+ JsonSchemaScalarTuple,
43
+ )
44
+ from vortex.tuple_json_schema.tuple_json_schema_test_tuples import (
45
+ JsonSchemaSelfRefTuple,
46
+ )
47
+
48
+
49
+ class TupleJsonSchemaTest(unittest.TestCase):
50
+ def testScalarsDatetimeAndOptional(self):
51
+ schema = JsonSchemaScalarTuple.tupleJsonSchema()
52
+ self.assertIsInstance(schema, JsonSchemaNodeTuple)
53
+
54
+ self.assertEqual(schema.schemaUri, JSON_SCHEMA_DRAFT_2020_12)
55
+ self.assertEqual(schema.schemaType, JSON_SCHEMA_TYPE_OBJECT)
56
+ self.assertEqual(schema.title, JsonSchemaScalarTuple.tupleType())
57
+ assert schema.properties is not None, (
58
+ "JsonSchemaScalarTuple schema.properties must be set"
59
+ )
60
+
61
+ self.assertEqual(
62
+ schema.properties["aString"].schemaType, JSON_SCHEMA_TYPE_STRING
63
+ )
64
+ self.assertEqual(
65
+ schema.properties["aInt"].schemaType, JSON_SCHEMA_TYPE_INTEGER
66
+ )
67
+ self.assertEqual(
68
+ schema.properties["aFloat"].schemaType, JSON_SCHEMA_TYPE_NUMBER
69
+ )
70
+ self.assertEqual(
71
+ schema.properties["aBool"].schemaType, JSON_SCHEMA_TYPE_BOOLEAN
72
+ )
73
+ self.assertEqual(
74
+ schema.properties["aDate"].schemaType, JSON_SCHEMA_TYPE_STRING
75
+ )
76
+ self.assertEqual(
77
+ schema.properties["aDate"].format, JSON_SCHEMA_FORMAT_DATE_TIME
78
+ )
79
+ self.assertEqual(
80
+ schema.properties["aOptional"].schemaType, JSON_SCHEMA_TYPE_STRING
81
+ )
82
+ self.assertEqual(
83
+ schema.properties["aBytes"].schemaType, JSON_SCHEMA_TYPE_STRING
84
+ )
85
+ self.assertEqual(
86
+ schema.properties["aBytes"].contentEncoding,
87
+ JSON_SCHEMA_CONTENT_ENCODING_BINARY,
88
+ )
89
+ self.assertEqual(
90
+ schema.properties["aDict"].schemaType, JSON_SCHEMA_TYPE_OBJECT
91
+ )
92
+
93
+ assert schema.required is not None, (
94
+ "JsonSchemaScalarTuple schema.required must be set"
95
+ )
96
+ self.assertIn("aString", schema.required)
97
+ self.assertIn("aDate", schema.required)
98
+ self.assertNotIn("aOptional", schema.required)
99
+
100
+ def testMissingTypingTypeFallsBackToString(self):
101
+ schema = JsonSchemaScalarTuple.tupleJsonSchema()
102
+ assert schema.properties is not None, (
103
+ "JsonSchemaScalarTuple schema.properties must be set for "
104
+ "missing typingType test"
105
+ )
106
+ self.assertEqual(
107
+ schema.properties["aMissingTyping"].schemaType,
108
+ JSON_SCHEMA_TYPE_STRING,
109
+ )
110
+
111
+ def testCommentBecomesDescription(self):
112
+ schema = JsonSchemaScalarTuple.tupleJsonSchema()
113
+ assert schema.properties is not None, (
114
+ "JsonSchemaScalarTuple schema.properties must be set for "
115
+ "comment description test"
116
+ )
117
+ aComment = schema.properties["aComment"]
118
+ self.assertEqual(aComment.description, "Field with a description")
119
+ self.assertEqual(aComment.schemaType, JSON_SCHEMA_TYPE_STRING)
120
+
121
+ def testNestedTupleDefsAndRef(self):
122
+ schema = JsonSchemaParentTuple.tupleJsonSchema()
123
+ nestedType = JsonSchemaNestedTuple.tupleType()
124
+ assert schema.properties is not None, (
125
+ "JsonSchemaParentTuple schema.properties must be set"
126
+ )
127
+ assert schema.defs is not None, (
128
+ "JsonSchemaParentTuple schema.defs must be set for nested tuple"
129
+ )
130
+
131
+ self.assertEqual(
132
+ schema.properties["child"].ref,
133
+ f"{JSON_SCHEMA_DEFS_REF_PREFIX}{nestedType}",
134
+ )
135
+ self.assertIn(nestedType, schema.defs)
136
+ nestedSchema = schema.defs[nestedType]
137
+ self.assertEqual(nestedSchema.schemaType, JSON_SCHEMA_TYPE_OBJECT)
138
+ self.assertEqual(nestedSchema.title, nestedType)
139
+ assert nestedSchema.properties is not None, (
140
+ "nested JsonSchemaNestedTuple defs entry properties must be set"
141
+ )
142
+ self.assertEqual(
143
+ nestedSchema.properties["nestedInt"].schemaType,
144
+ JSON_SCHEMA_TYPE_INTEGER,
145
+ )
146
+
147
+ def testListOfNestedTuple(self):
148
+ schema = JsonSchemaParentTuple.tupleJsonSchema()
149
+ nestedType = JsonSchemaNestedTuple.tupleType()
150
+ assert schema.properties is not None, (
151
+ "JsonSchemaParentTuple schema.properties must be set for list test"
152
+ )
153
+ children = schema.properties["children"]
154
+
155
+ self.assertEqual(children.schemaType, JSON_SCHEMA_TYPE_ARRAY)
156
+ assert children.items is not None, (
157
+ "JsonSchemaParentTuple children.items must be set"
158
+ )
159
+ self.assertEqual(
160
+ children.items.ref, f"{JSON_SCHEMA_DEFS_REF_PREFIX}{nestedType}"
161
+ )
162
+
163
+ def testBareListTypingTypeIsArray(self):
164
+ schema = JsonSchemaBareListTuple.tupleJsonSchema()
165
+ assert schema.properties is not None, (
166
+ "JsonSchemaBareListTuple schema.properties must be set"
167
+ )
168
+ items = schema.properties["items"]
169
+
170
+ self.assertEqual(items.schemaType, JSON_SCHEMA_TYPE_ARRAY)
171
+ assert items.items is not None, (
172
+ "JsonSchemaBareListTuple items.items must be set"
173
+ )
174
+ self.assertEqual(items.items.schemaType, JSON_SCHEMA_TYPE_STRING)
175
+
176
+ def testSelfReferenceDoesNotInfiniteLoop(self):
177
+ schema = JsonSchemaSelfRefTuple.tupleJsonSchema()
178
+ selfType = JsonSchemaSelfRefTuple.tupleType()
179
+ assert schema.properties is not None, (
180
+ "JsonSchemaSelfRefTuple schema.properties must be set"
181
+ )
182
+ assert schema.defs is not None, (
183
+ "JsonSchemaSelfRefTuple schema.defs must be set for self-ref"
184
+ )
185
+
186
+ self.assertEqual(
187
+ schema.properties["parent"].ref,
188
+ f"{JSON_SCHEMA_DEFS_REF_PREFIX}{selfType}",
189
+ )
190
+ self.assertIn(selfType, schema.defs)
191
+ selfDefs = schema.defs[selfType]
192
+ self.assertEqual(selfDefs.schemaType, JSON_SCHEMA_TYPE_OBJECT)
193
+ assert selfDefs.properties is not None, (
194
+ "self-ref defs entry properties must be set"
195
+ )
196
+ self.assertEqual(
197
+ selfDefs.properties["parent"].ref,
198
+ f"{JSON_SCHEMA_DEFS_REF_PREFIX}{selfType}",
199
+ )
200
+ self.assertNotIn("parent", schema.required or [])
201
+
202
+ def testBuildJsonSchemaDictKeys(self):
203
+ schema = JsonSchemaScalarTuple.tupleJsonSchema()
204
+ schemaDict = schema.buildJsonSchemaDict()
205
+
206
+ self.assertEqual(
207
+ schemaDict[JSON_SCHEMA_KEY_SCHEMA], JSON_SCHEMA_DRAFT_2020_12
208
+ )
209
+ self.assertEqual(
210
+ schemaDict[JSON_SCHEMA_KEY_TYPE], JSON_SCHEMA_TYPE_OBJECT
211
+ )
212
+ self.assertEqual(
213
+ schemaDict[JSON_SCHEMA_KEY_TITLE],
214
+ JsonSchemaScalarTuple.tupleType(),
215
+ )
216
+ properties = schemaDict[JSON_SCHEMA_KEY_PROPERTIES]
217
+ self.assertEqual(
218
+ properties["aDate"],
219
+ {
220
+ JSON_SCHEMA_KEY_TYPE: JSON_SCHEMA_TYPE_STRING,
221
+ JSON_SCHEMA_KEY_FORMAT: JSON_SCHEMA_FORMAT_DATE_TIME,
222
+ },
223
+ )
224
+ self.assertEqual(
225
+ properties["aComment"][JSON_SCHEMA_KEY_DESCRIPTION],
226
+ "Field with a description",
227
+ )
228
+ self.assertIn("aString", schemaDict[JSON_SCHEMA_KEY_REQUIRED])
229
+
230
+ def testBuildJsonSchemaDictNestedRef(self):
231
+ schema = JsonSchemaParentTuple.tupleJsonSchema()
232
+ schemaDict = schema.buildJsonSchemaDict()
233
+ nestedType = JsonSchemaNestedTuple.tupleType()
234
+
235
+ self.assertEqual(
236
+ schemaDict[JSON_SCHEMA_KEY_PROPERTIES]["child"],
237
+ {
238
+ JSON_SCHEMA_KEY_REF: (
239
+ f"{JSON_SCHEMA_DEFS_REF_PREFIX}{nestedType}"
240
+ )
241
+ },
242
+ )
243
+ self.assertIn(nestedType, schemaDict[JSON_SCHEMA_KEY_DEFS])
244
+ self.assertEqual(
245
+ schemaDict[JSON_SCHEMA_KEY_PROPERTIES]["children"][
246
+ JSON_SCHEMA_KEY_ITEMS
247
+ ],
248
+ {
249
+ JSON_SCHEMA_KEY_REF: (
250
+ f"{JSON_SCHEMA_DEFS_REF_PREFIX}{nestedType}"
251
+ )
252
+ },
253
+ )
254
+ self.assertEqual(
255
+ schemaDict[JSON_SCHEMA_KEY_DEFS][nestedType][JSON_SCHEMA_KEY_TYPE],
256
+ JSON_SCHEMA_TYPE_OBJECT,
257
+ )
@@ -0,0 +1,69 @@
1
+ # Copyright (c) 2016-2026 Flying Ion Pty Ltd
2
+ #
3
+ # This software is open source; the MIT license applies.
4
+ # See LICENSE for the full license text.
5
+ #
6
+ # Website : https://flyingion.com
7
+ # Support : contact@flyingion.com
8
+
9
+ """Fixture tuples for ``tuple_json_schema_test``."""
10
+
11
+ from datetime import datetime
12
+ from datetime import timezone
13
+
14
+ from vortex.Tuple import Tuple
15
+ from vortex.Tuple import TupleField
16
+ from vortex.Tuple import addTupleType
17
+
18
+
19
+ @addTupleType
20
+ class JsonSchemaNestedTuple(Tuple):
21
+ __tupleType__ = "flyingion.vortex.JsonSchemaNestedTuple"
22
+ nestedInt = TupleField(defaultValue=0, typingType=int)
23
+
24
+
25
+ @addTupleType
26
+ class JsonSchemaScalarTuple(Tuple):
27
+ __tupleType__ = "flyingion.vortex.JsonSchemaScalarTuple"
28
+ aString = TupleField(defaultValue="", typingType=str)
29
+ aInt = TupleField(defaultValue=0, typingType=int)
30
+ aFloat = TupleField(defaultValue=0.0, typingType=float)
31
+ aBool = TupleField(defaultValue=False, typingType=bool)
32
+ aDate = TupleField(
33
+ defaultValue=datetime(2020, 1, 1, tzinfo=timezone.utc),
34
+ typingType=datetime,
35
+ )
36
+ aOptional = TupleField(typingType=str | None)
37
+ aBytes = TupleField(defaultValue=b"", typingType=bytes)
38
+ aDict = TupleField(defaultValue={}, typingType=dict)
39
+ aComment = TupleField(
40
+ defaultValue="",
41
+ typingType=str,
42
+ comment="Field with a description",
43
+ )
44
+ aMissingTyping = TupleField(defaultValue="")
45
+
46
+
47
+ @addTupleType
48
+ class JsonSchemaParentTuple(Tuple):
49
+ __tupleType__ = "flyingion.vortex.JsonSchemaParentTuple"
50
+ child = TupleField(typingType=JsonSchemaNestedTuple)
51
+ children = TupleField(
52
+ defaultValue=[], typingType=list[JsonSchemaNestedTuple]
53
+ )
54
+
55
+
56
+ @addTupleType
57
+ class JsonSchemaBareListTuple(Tuple):
58
+ __tupleType__ = "flyingion.vortex.JsonSchemaBareListTuple"
59
+ items = TupleField(defaultValue=[], typingType=list)
60
+
61
+
62
+ @addTupleType
63
+ class JsonSchemaSelfRefTuple(Tuple):
64
+ __tupleType__ = "flyingion.vortex.JsonSchemaSelfRefTuple"
65
+ label = TupleField(defaultValue="", typingType=str)
66
+ parent = TupleField()
67
+
68
+
69
+ JsonSchemaSelfRefTuple.parent.typingType = JsonSchemaSelfRefTuple | None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vortexpy
3
- Version: 4.0.29
3
+ Version: 4.1.2
4
4
  Summary: Flying Ion's observable, routable, data serialisation and transport code.
5
5
  Author-email: Flying Ion Pty Ltd <contact@flyingion.com>
6
6
  License: MIT
@@ -92,6 +92,12 @@ vortex/sqla_orm/VortexTupleJson.py
92
92
  vortex/sqla_orm/__init__.py
93
93
  vortex/storage/TupleStorageSqlite.py
94
94
  vortex/storage/__init__.py
95
+ vortex/tuple_json_schema/__init__.py
96
+ vortex/tuple_json_schema/constants.py
97
+ vortex/tuple_json_schema/json_schema_node_tuple.py
98
+ vortex/tuple_json_schema/tuple_json_schema_builder.py
99
+ vortex/tuple_json_schema/tuple_json_schema_test.py
100
+ vortex/tuple_json_schema/tuple_json_schema_test_tuples.py
95
101
  vortexpy.egg-info/PKG-INFO
96
102
  vortexpy.egg-info/SOURCES.txt
97
103
  vortexpy.egg-info/dependency_links.txt
@@ -1,9 +0,0 @@
1
- # Copyright (c) 2016-2026 Flying Ion Pty Ltd
2
- #
3
- # This software is open source; the MIT license applies.
4
- # See LICENSE for the full license text.
5
- #
6
- # Website : https://flyingion.com
7
- # Support : contact@flyingion.com
8
-
9
- __version__ = '4.0.29'
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes