vortexpy 6.0.1__tar.gz → 6.1.0__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.
- {vortexpy-6.0.1 → vortexpy-6.1.0}/PKG-INFO +1 -1
- {vortexpy-6.0.1 → vortexpy-6.1.0}/pyproject.toml +1 -1
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/SerialiseUtil.py +8 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/Tuple.py +46 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/TupleDocumentConverter.py +3 -4
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/TupleTest.py +79 -1
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/VortexFactory.py +112 -128
- vortexpy-6.1.0/vortex/VortexServerTcp.py +213 -0
- vortexpy-6.1.0/vortex/VortexServerTcp_test.py +121 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/VortexServerWebsocket.py +67 -26
- vortexpy-6.1.0/vortex/VortexServerWebsocket_test.py +116 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/VortexSession.py +10 -8
- vortexpy-6.1.0/vortex/VortexSession_test.py +57 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/__init__.py +1 -1
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/data_loader/TupleDataLoaderDelegate.py +1 -1
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/handler/TupleDataActionClient.py +19 -11
- vortexpy-6.1.0/vortex/handler/TupleDataActionClient_test.py +102 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/handler/TupleDataObservableHandler.py +10 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/rpc/RPC.py +1 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortexpy.egg-info/PKG-INFO +1 -1
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortexpy.egg-info/SOURCES.txt +2 -0
- vortexpy-6.0.1/vortex/VortexServerTcp.py +0 -133
- vortexpy-6.0.1/vortex/VortexServerWebsocket_test.py +0 -22
- vortexpy-6.0.1/vortex/VortexSession_test.py +0 -23
- {vortexpy-6.0.1 → vortexpy-6.1.0}/LICENSE +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/README.md +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/example/TwistedDeferredTimeout.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/example/__init__.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/example/run_http_upgraded_websocket_ping_client.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/example/run_http_upgraded_websocket_ping_server.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/example/run_http_upgraded_websocket_rpc_test_client.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/example/run_http_upgraded_websocket_rpc_test_server.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/example/run_websocket_rpc_test_client.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/example/run_websocket_rpc_test_server.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/example/setup_example_logging.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/example/store_and_load_json.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/setup.cfg +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/DataWrapTuple.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/DeferUtil.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/DeferUtil_test.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/Jsonable.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/MtlsAuthorizedClientRegistry.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/MtlsAuthorizedClientRegistry_test.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/MtlsServerSslOptions.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/Payload.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/PayloadEndpoint.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/PayloadEndpointTest.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/PayloadEnvelope.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/PayloadFilterKeys.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/PayloadIO.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/PayloadPriority.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/PayloadResponse.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/PayloadResponseTest.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/PayloadResponse_test.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/PayloadTest.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/PerformTestActionTuple.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/SerialiseUtil_test.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/TestTuple.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/TupleAction.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/TupleBson.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/TupleBson_test.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/TupleDataForTest.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/TuplePerfTest.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/TuplePolymorphicTupleTypeFieldArgTest.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/TupleRestful.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/TupleRestful_test.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/TupleSelector.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/VortexABC.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/VortexAccessControlContextABC.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/VortexAuthenticatorABC.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/VortexClientHttp.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/VortexClientHttpTest.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/VortexClientTcp.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/VortexClientWebsocketFactory.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/VortexConnectionABC.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/VortexEndpointPermission.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/VortexFactory_test.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/VortexPayloadProtocol.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/VortexPermission.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/VortexPermissionError.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/VortexServer.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/VortexServerConnection.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/VortexServerConnection_test.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/VortexServerHttpResource.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/VortexServer_test.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/VortexTcpMemoryLeakPayloadTest.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/VortexTcpMemoryLeakRawTest.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/VortexTest.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/VortexUserDetailsABC.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/VortexUtil.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/VortexWritePushProducer.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/VortexWritePushProducer_test.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/data_loader/TupleDataLiveLockManager.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/data_loader/TupleDataLiveLockManager_test.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/data_loader/TupleDataLoader.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/data_loader/TupleDataLoaderTupleABC.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/data_loader/TupleDataLoaderTuples.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/data_loader/__init__.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/event_bus/TupleChangeEventABC.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/event_bus/TupleChangeEventBus.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/event_bus/TupleChangeEventBusObserverABC.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/event_bus/__init__.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/handler/TupleActionProcessor.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/handler/TupleActionProcessorProxy.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/handler/TupleActionProcessorTestHandler.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/handler/TupleActionProcessor_shutdown_test.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/handler/TupleActionProcessor_test.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/handler/TupleActionPushOffline.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/handler/TupleActionPushOffline_test.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/handler/TupleDataAccessGranterABC.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/handler/TupleDataObservableCache.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/handler/TupleDataObservableCache_test.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/handler/TupleDataObservableHandler_test.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/handler/TupleDataObservableProxyHandler.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/handler/TupleDataObservableProxyHandler_test.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/handler/TupleDataObservableTestHandler.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/handler/TupleDataObserverClient.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/handler/TupleDataProviderABC.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/handler/VortexJSTupleLoaderTestHandler.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/handler/__init__.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/mypy_plugin.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/payload_many_tuples_benchmark_test.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/py.typed +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/restful/GzippedDataHttpClient.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/restful/GzippedDataHttpClientTest.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/restful/GzippedPayloadHttpClient.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/restful/RestfulHttpClient.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/restful/RestfulResource.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/restful/__init__.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/restful/restful_http_client_test.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/restful/restful_resource_test.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/rpc/RPCTest.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/rpc/RPC_test.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/rpc/__init__.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/sqla_orm/OrmCrudHandler.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/sqla_orm/TupleProviderForDb.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/sqla_orm/VortexTupleJson.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/sqla_orm/__init__.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/storage/TupleActionStorageSqlite.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/storage/TupleStorageSqlite.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/storage/__init__.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/tuple_json_schema/__init__.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/tuple_json_schema/constants.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/tuple_json_schema/json_schema_node_tuple.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/tuple_json_schema/tuple_json_schema_builder.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/tuple_json_schema/tuple_json_schema_test.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/tuple_json_schema/tuple_json_schema_test_tuples.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/tuple_test_fixtures.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/twisted_runtime_metrics.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortex/twisted_runtime_metrics_test.py +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortexpy.egg-info/dependency_links.txt +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortexpy.egg-info/entry_points.txt +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortexpy.egg-info/requires.txt +0 -0
- {vortexpy-6.0.1 → vortexpy-6.1.0}/vortexpy.egg-info/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "vortexpy"
|
|
7
|
-
version = "6.0
|
|
7
|
+
version = "6.1.0"
|
|
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."
|
|
@@ -290,10 +290,18 @@ def fromRapuiType(typeName: str):
|
|
|
290
290
|
def utilTypeHintsForClass(cls: type) -> dict[str, typing.Any]:
|
|
291
291
|
"""Resolved type hints for a class, cached per class.
|
|
292
292
|
|
|
293
|
+
Tuple classes resolve through ``Tuple.typeHints()`` — the canonical
|
|
294
|
+
per-class store; other classes are cached by this function's
|
|
295
|
+
``lru_cache``.
|
|
296
|
+
|
|
293
297
|
Returns an empty dict when hints can not be resolved (for example
|
|
294
298
|
unresolvable forward references) — enum coercion degrades to no-op.
|
|
295
299
|
"""
|
|
300
|
+
from vortex.Tuple import Tuple
|
|
301
|
+
|
|
296
302
|
try:
|
|
303
|
+
if issubclass(cls, Tuple):
|
|
304
|
+
return cls.typeHints()
|
|
297
305
|
return typing.get_type_hints(cls)
|
|
298
306
|
except Exception:
|
|
299
307
|
return {}
|
|
@@ -17,6 +17,7 @@ from datetime import datetime
|
|
|
17
17
|
from typing import Dict, Optional, Any
|
|
18
18
|
from typing import List
|
|
19
19
|
from typing import Type
|
|
20
|
+
from typing import get_type_hints
|
|
20
21
|
|
|
21
22
|
from sqlalchemy.orm.attributes import InstrumentedAttribute
|
|
22
23
|
from sqlalchemy.orm.properties import RelationshipProperty
|
|
@@ -648,6 +649,51 @@ class Tuple(Jsonable, _TupleToPlainJsonMixin, _TupleToSqlaJsonMixin):
|
|
|
648
649
|
def tupleFieldNames(cls) -> List[str]:
|
|
649
650
|
return cls.__fieldNames__
|
|
650
651
|
|
|
652
|
+
@classmethod
|
|
653
|
+
def typeHints(cls) -> Dict[str, Any]:
|
|
654
|
+
"""Type Hints
|
|
655
|
+
|
|
656
|
+
Resolved type hints for this class, computed once and stored on the
|
|
657
|
+
class itself.
|
|
658
|
+
|
|
659
|
+
Resolution is lazy: tuples reference each other with forward
|
|
660
|
+
references, so hints can only be resolved after all imports
|
|
661
|
+
complete. Failures are not stored — if the hints are unresolvable
|
|
662
|
+
the exception propagates and the next call retries.
|
|
663
|
+
|
|
664
|
+
The hints are stored in the class's own ``__dict__`` so a subclass
|
|
665
|
+
always resolves and stores its own hints, never a base class's.
|
|
666
|
+
|
|
667
|
+
:return: The resolved type hints for this class.
|
|
668
|
+
"""
|
|
669
|
+
cachedHints = cls.__dict__.get("__cachedTypeHints__")
|
|
670
|
+
if cachedHints is not None:
|
|
671
|
+
return cachedHints
|
|
672
|
+
|
|
673
|
+
hints = get_type_hints(cls)
|
|
674
|
+
cls.__cachedTypeHints__ = hints
|
|
675
|
+
return hints
|
|
676
|
+
|
|
677
|
+
@classmethod
|
|
678
|
+
def typeHintsWithExtras(cls) -> Dict[str, Any]:
|
|
679
|
+
"""Type Hints With Extras
|
|
680
|
+
|
|
681
|
+
Resolved type hints for this class, including ``Annotated`` extras,
|
|
682
|
+
computed once and stored on the class itself.
|
|
683
|
+
|
|
684
|
+
Resolution is lazy and failures are not stored, same as
|
|
685
|
+
:meth:`typeHints`.
|
|
686
|
+
|
|
687
|
+
:return: The resolved type hints for this class, with extras.
|
|
688
|
+
"""
|
|
689
|
+
cachedHints = cls.__dict__.get("__cachedTypeHintsWithExtras__")
|
|
690
|
+
if cachedHints is not None:
|
|
691
|
+
return cachedHints
|
|
692
|
+
|
|
693
|
+
hints = get_type_hints(cls, include_extras=True)
|
|
694
|
+
cls.__cachedTypeHintsWithExtras__ = hints
|
|
695
|
+
return hints
|
|
696
|
+
|
|
651
697
|
@classmethod
|
|
652
698
|
def tupleJsonSchema(cls) -> "JsonSchemaNodeTuple":
|
|
653
699
|
"""Tuple JSON Schema
|
|
@@ -20,7 +20,6 @@ from typing import TYPE_CHECKING, Annotated, Any, NamedTuple, Optional, Type
|
|
|
20
20
|
|
|
21
21
|
from typing import Union, get_args
|
|
22
22
|
from typing import get_origin
|
|
23
|
-
from typing import get_type_hints
|
|
24
23
|
|
|
25
24
|
from .Tuple import BsonDatetimeFormatE
|
|
26
25
|
from .Tuple import TUPLE_TYPES_BY_NAME
|
|
@@ -751,7 +750,7 @@ class TupleDocumentConverterBase:
|
|
|
751
750
|
) -> TupleValidationResult:
|
|
752
751
|
errors: list[TupleValidationError] = []
|
|
753
752
|
|
|
754
|
-
if not
|
|
753
|
+
if not TupleClass.typeHints():
|
|
755
754
|
errors.append(
|
|
756
755
|
cls._makeError(_ROOT_PATH, f"{TupleClass} has no annotations")
|
|
757
756
|
)
|
|
@@ -759,8 +758,8 @@ class TupleDocumentConverterBase:
|
|
|
759
758
|
|
|
760
759
|
TupleClass = cls._resolveConcreteClass(TupleClass, jsonDict, errors)
|
|
761
760
|
|
|
762
|
-
typeHints =
|
|
763
|
-
extraTypeHits =
|
|
761
|
+
typeHints = TupleClass.typeHints()
|
|
762
|
+
extraTypeHits = TupleClass.typeHintsWithExtras()
|
|
764
763
|
|
|
765
764
|
newTuple = TupleClass()
|
|
766
765
|
|
|
@@ -9,10 +9,13 @@
|
|
|
9
9
|
import datetime
|
|
10
10
|
import json
|
|
11
11
|
|
|
12
|
+
from typing import Annotated, get_type_hints
|
|
13
|
+
from unittest import mock
|
|
14
|
+
|
|
12
15
|
from twisted.trial import unittest
|
|
13
16
|
|
|
14
17
|
from vortex.TestTuple import TestSubTuple, TestTuple
|
|
15
|
-
from vortex.Tuple import TupleHash
|
|
18
|
+
from vortex.Tuple import Tuple, TupleField, TupleHash
|
|
16
19
|
|
|
17
20
|
|
|
18
21
|
def makeTupleA():
|
|
@@ -124,3 +127,78 @@ class TuplePyTest(unittest.TestCase):
|
|
|
124
127
|
self.assertFalse(
|
|
125
128
|
hashA1 in {hashB1: None}, "TupleHash in fail, A1 in {B1:None}"
|
|
126
129
|
)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class TupleTypeHintsTest(unittest.TestCase):
|
|
133
|
+
"""Tests for the lazy per-class type hint storage on ``Tuple``."""
|
|
134
|
+
|
|
135
|
+
def testTypeHintsCachedOnClass(self):
|
|
136
|
+
"""Two calls return the same dict; get_type_hints runs once."""
|
|
137
|
+
|
|
138
|
+
class CachedHintsTuple(Tuple):
|
|
139
|
+
aInt: int = TupleField()
|
|
140
|
+
aStr: str = TupleField()
|
|
141
|
+
|
|
142
|
+
countingHints = mock.Mock(side_effect=get_type_hints)
|
|
143
|
+
with mock.patch("vortex.Tuple.get_type_hints", countingHints):
|
|
144
|
+
hints1 = CachedHintsTuple.typeHints()
|
|
145
|
+
hints2 = CachedHintsTuple.typeHints()
|
|
146
|
+
|
|
147
|
+
self.assertIs(hints1, hints2)
|
|
148
|
+
self.assertEqual(countingHints.call_count, 1)
|
|
149
|
+
self.assertIs(hints1["aInt"], int)
|
|
150
|
+
self.assertIs(hints1["aStr"], str)
|
|
151
|
+
|
|
152
|
+
def testTypeHintsWithExtrasCachedOnClass(self):
|
|
153
|
+
"""Annotated extras are kept; get_type_hints runs once."""
|
|
154
|
+
|
|
155
|
+
class CachedExtrasTuple(Tuple):
|
|
156
|
+
aInt: Annotated[int, "marker"] = TupleField()
|
|
157
|
+
|
|
158
|
+
countingHints = mock.Mock(side_effect=get_type_hints)
|
|
159
|
+
with mock.patch("vortex.Tuple.get_type_hints", countingHints):
|
|
160
|
+
hints1 = CachedExtrasTuple.typeHintsWithExtras()
|
|
161
|
+
hints2 = CachedExtrasTuple.typeHintsWithExtras()
|
|
162
|
+
|
|
163
|
+
self.assertIs(hints1, hints2)
|
|
164
|
+
self.assertEqual(countingHints.call_count, 1)
|
|
165
|
+
self.assertEqual(
|
|
166
|
+
countingHints.call_args, mock.call(CachedExtrasTuple, include_extras=True)
|
|
167
|
+
)
|
|
168
|
+
self.assertEqual(hints1["aInt"].__metadata__, ("marker",))
|
|
169
|
+
|
|
170
|
+
def testTypeHintsSubclassStoresOwnHints(self):
|
|
171
|
+
"""A subclass never returns a base class's cached hints."""
|
|
172
|
+
|
|
173
|
+
class ParentHintsTuple(Tuple):
|
|
174
|
+
aStr: str = TupleField()
|
|
175
|
+
|
|
176
|
+
class ChildHintsTuple(ParentHintsTuple):
|
|
177
|
+
aInt: int = TupleField()
|
|
178
|
+
|
|
179
|
+
parentHints = ParentHintsTuple.typeHints()
|
|
180
|
+
childHints = ChildHintsTuple.typeHints()
|
|
181
|
+
|
|
182
|
+
self.assertIsNot(parentHints, childHints)
|
|
183
|
+
self.assertIs(parentHints["aStr"], str)
|
|
184
|
+
self.assertNotIn("aInt", parentHints)
|
|
185
|
+
self.assertIs(childHints["aStr"], str)
|
|
186
|
+
self.assertIs(childHints["aInt"], int)
|
|
187
|
+
|
|
188
|
+
def testTypeHintsFailureNotCached(self):
|
|
189
|
+
"""A failed resolution propagates and the next call retries."""
|
|
190
|
+
|
|
191
|
+
class FailingHintsTuple(Tuple):
|
|
192
|
+
aInt: int = TupleField()
|
|
193
|
+
|
|
194
|
+
flakyHints = mock.Mock(
|
|
195
|
+
side_effect=[NameError("unresolvable"), {"aInt": int}]
|
|
196
|
+
)
|
|
197
|
+
with mock.patch("vortex.Tuple.get_type_hints", flakyHints):
|
|
198
|
+
self.assertRaises(NameError, FailingHintsTuple.typeHints)
|
|
199
|
+
hints = FailingHintsTuple.typeHints()
|
|
200
|
+
again = FailingHintsTuple.typeHints()
|
|
201
|
+
|
|
202
|
+
self.assertEqual(hints, {"aInt": int})
|
|
203
|
+
self.assertIs(hints, again)
|
|
204
|
+
self.assertEqual(flakyHints.call_count, 2)
|