vortexpy 4.3.7__tar.gz → 4.3.10__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-4.3.7 → vortexpy-4.3.10}/PKG-INFO +1 -1
- {vortexpy-4.3.7 → vortexpy-4.3.10}/pyproject.toml +1 -1
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/SerialiseUtil.py +8 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/Tuple.py +71 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/TupleBson.py +7 -25
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/TupleBson_test.py +27 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/TupleDocumentConverter.py +3 -4
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/TupleTest.py +79 -1
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/VortexServer.py +9 -2
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/__init__.py +1 -1
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortexpy.egg-info/PKG-INFO +1 -1
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortexpy.egg-info/SOURCES.txt +0 -1
- vortexpy-4.3.7/LICENSE +0 -22
- {vortexpy-4.3.7 → vortexpy-4.3.10}/README.md +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/example/TwistedDeferredTimeout.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/example/__init__.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/example/run_http_upgraded_websocket_ping_client.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/example/run_http_upgraded_websocket_ping_server.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/example/run_http_upgraded_websocket_rpc_test_client.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/example/run_http_upgraded_websocket_rpc_test_server.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/example/run_websocket_rpc_test_client.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/example/run_websocket_rpc_test_server.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/example/setup_example_logging.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/example/store_and_load_json.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/setup.cfg +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/DataWrapTuple.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/DeferUtil.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/DeferUtil_test.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/Jsonable.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/MtlsAuthorizedClientRegistry.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/MtlsAuthorizedClientRegistry_test.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/MtlsServerSslOptions.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/Payload.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/PayloadEndpoint.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/PayloadEndpointTest.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/PayloadEnvelope.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/PayloadFilterKeys.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/PayloadIO.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/PayloadPriority.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/PayloadResponse.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/PayloadResponseTest.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/PayloadResponse_test.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/PayloadTest.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/PerformTestActionTuple.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/SerialiseUtil_test.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/TestTuple.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/TupleAction.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/TupleDataForTest.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/TuplePerfTest.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/TuplePolymorphicTupleTypeFieldArgTest.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/TupleRestful.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/TupleRestful_test.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/TupleSelector.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/VortexABC.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/VortexAccessControlContextABC.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/VortexAuthenticatorABC.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/VortexClientHttp.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/VortexClientHttpTest.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/VortexClientTcp.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/VortexClientWebsocketFactory.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/VortexConnectionABC.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/VortexEndpointPermission.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/VortexFactory.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/VortexFactory_test.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/VortexPayloadProtocol.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/VortexPermission.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/VortexPermissionError.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/VortexServerConnection.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/VortexServerConnection_test.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/VortexServerHttpResource.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/VortexServerTcp.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/VortexServerWebsocket.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/VortexServerWebsocket_test.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/VortexServer_test.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/VortexSession.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/VortexSession_test.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/VortexTcpMemoryLeakPayloadTest.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/VortexTcpMemoryLeakRawTest.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/VortexTest.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/VortexUtil.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/VortexWritePushProducer.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/VortexWritePushProducer_test.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/data_loader/TupleDataLiveLockManager.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/data_loader/TupleDataLoader.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/data_loader/TupleDataLoaderDelegate.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/data_loader/TupleDataLoaderTupleABC.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/data_loader/TupleDataLoaderTuples.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/data_loader/__init__.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/event_bus/TupleChangeEventABC.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/event_bus/TupleChangeEventBus.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/event_bus/TupleChangeEventBusObserverABC.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/event_bus/__init__.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/handler/TupleActionProcessor.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/handler/TupleActionProcessorProxy.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/handler/TupleActionProcessorTestHandler.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/handler/TupleActionProcessor_shutdown_test.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/handler/TupleActionPushOffline.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/handler/TupleActionPushOffline_test.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/handler/TupleDataAccessGranterABC.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/handler/TupleDataActionClient.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/handler/TupleDataObservableCache.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/handler/TupleDataObservableCache_test.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/handler/TupleDataObservableHandler.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/handler/TupleDataObservableHandler_test.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/handler/TupleDataObservableProxyHandler.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/handler/TupleDataObservableProxyHandler_test.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/handler/TupleDataObservableTestHandler.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/handler/TupleDataObserverClient.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/handler/TupleDataProviderABC.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/handler/VortexJSTupleLoaderTestHandler.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/handler/__init__.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/mypy_plugin.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/payload_many_tuples_benchmark_test.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/py.typed +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/restful/GzippedDataHttpClient.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/restful/GzippedDataHttpClientTest.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/restful/GzippedPayloadHttpClient.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/restful/RestfulHttpClient.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/restful/RestfulResource.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/restful/__init__.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/restful/restful_http_client_test.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/restful/restful_resource_test.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/rpc/RPC.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/rpc/RPCTest.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/rpc/RPC_test.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/rpc/__init__.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/sqla_orm/OrmCrudHandler.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/sqla_orm/TupleProviderForDb.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/sqla_orm/VortexTupleJson.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/sqla_orm/__init__.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/storage/TupleActionStorageSqlite.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/storage/TupleStorageSqlite.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/storage/__init__.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/tuple_json_schema/__init__.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/tuple_json_schema/constants.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/tuple_json_schema/json_schema_node_tuple.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/tuple_json_schema/tuple_json_schema_builder.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/tuple_json_schema/tuple_json_schema_test.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/tuple_json_schema/tuple_json_schema_test_tuples.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/tuple_test_fixtures.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/twisted_runtime_metrics.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/twisted_runtime_metrics_test.py +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortexpy.egg-info/dependency_links.txt +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortexpy.egg-info/entry_points.txt +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/vortexpy.egg-info/requires.txt +0 -0
- {vortexpy-4.3.7 → vortexpy-4.3.10}/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 = "4.3.
|
|
7
|
+
version = "4.3.10"
|
|
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
|
|
@@ -28,6 +29,9 @@ from .SerialiseUtil import ISO8601
|
|
|
28
29
|
from .SerialiseUtil import T_RAPUI_TUPLE
|
|
29
30
|
from .SerialiseUtil import WKBElement
|
|
30
31
|
from .SerialiseUtil import convertFromWkbElement
|
|
32
|
+
from .SerialiseUtil import datetimeToBson
|
|
33
|
+
from .SerialiseUtil import datetimeToBsonMicroseconds
|
|
34
|
+
from .SerialiseUtil import requireAwareDatetime
|
|
31
35
|
|
|
32
36
|
logger = logging.getLogger(__name__)
|
|
33
37
|
|
|
@@ -312,6 +316,28 @@ class TupleField:
|
|
|
312
316
|
self.jsonExclude = jsonExclude
|
|
313
317
|
self.bsonDatetimeFormat = bsonDatetimeFormat
|
|
314
318
|
|
|
319
|
+
def toBson(self, value: Any) -> Any:
|
|
320
|
+
"""Convert a datetime to this field's BSON storage format.
|
|
321
|
+
|
|
322
|
+
Non-datetime values pass through unchanged, so every Mongo
|
|
323
|
+
filter value can be wrapped uniformly. Naive datetimes raise
|
|
324
|
+
``TypeError``.
|
|
325
|
+
"""
|
|
326
|
+
if not isinstance(value, datetime):
|
|
327
|
+
return value
|
|
328
|
+
aware = requireAwareDatetime(value)
|
|
329
|
+
if self.bsonDatetimeFormat is None:
|
|
330
|
+
return datetimeToBson(aware)
|
|
331
|
+
if self.bsonDatetimeFormat is BsonDatetimeFormatE.standardMsIntEpoch:
|
|
332
|
+
return datetimeToBson(aware)
|
|
333
|
+
if self.bsonDatetimeFormat is BsonDatetimeFormatE.standardUsIntEpoch:
|
|
334
|
+
return datetimeToBsonMicroseconds(aware)
|
|
335
|
+
if self.bsonDatetimeFormat is BsonDatetimeFormatE.standardMsBsonDate:
|
|
336
|
+
return aware
|
|
337
|
+
raise NotImplementedError(
|
|
338
|
+
f"Unsupported BsonDatetimeFormatE {self.bsonDatetimeFormat!r}"
|
|
339
|
+
)
|
|
340
|
+
|
|
315
341
|
|
|
316
342
|
class PolymorphicTupleTypeFieldArg:
|
|
317
343
|
def __init__(self, fieldName: str):
|
|
@@ -642,6 +668,51 @@ class Tuple(Jsonable, _TupleToPlainJsonMixin, _TupleToSqlaJsonMixin):
|
|
|
642
668
|
def tupleFieldNames(cls) -> List[str]:
|
|
643
669
|
return cls.__fieldNames__
|
|
644
670
|
|
|
671
|
+
@classmethod
|
|
672
|
+
def typeHints(cls) -> Dict[str, Any]:
|
|
673
|
+
"""Type Hints
|
|
674
|
+
|
|
675
|
+
Resolved type hints for this class, computed once and stored on the
|
|
676
|
+
class itself.
|
|
677
|
+
|
|
678
|
+
Resolution is lazy: tuples reference each other with forward
|
|
679
|
+
references, so hints can only be resolved after all imports
|
|
680
|
+
complete. Failures are not stored — if the hints are unresolvable
|
|
681
|
+
the exception propagates and the next call retries.
|
|
682
|
+
|
|
683
|
+
The hints are stored in the class's own ``__dict__`` so a subclass
|
|
684
|
+
always resolves and stores its own hints, never a base class's.
|
|
685
|
+
|
|
686
|
+
:return: The resolved type hints for this class.
|
|
687
|
+
"""
|
|
688
|
+
cachedHints = cls.__dict__.get("__cachedTypeHints__")
|
|
689
|
+
if cachedHints is not None:
|
|
690
|
+
return cachedHints
|
|
691
|
+
|
|
692
|
+
hints = get_type_hints(cls)
|
|
693
|
+
cls.__cachedTypeHints__ = hints
|
|
694
|
+
return hints
|
|
695
|
+
|
|
696
|
+
@classmethod
|
|
697
|
+
def typeHintsWithExtras(cls) -> Dict[str, Any]:
|
|
698
|
+
"""Type Hints With Extras
|
|
699
|
+
|
|
700
|
+
Resolved type hints for this class, including ``Annotated`` extras,
|
|
701
|
+
computed once and stored on the class itself.
|
|
702
|
+
|
|
703
|
+
Resolution is lazy and failures are not stored, same as
|
|
704
|
+
:meth:`typeHints`.
|
|
705
|
+
|
|
706
|
+
:return: The resolved type hints for this class, with extras.
|
|
707
|
+
"""
|
|
708
|
+
cachedHints = cls.__dict__.get("__cachedTypeHintsWithExtras__")
|
|
709
|
+
if cachedHints is not None:
|
|
710
|
+
return cachedHints
|
|
711
|
+
|
|
712
|
+
hints = get_type_hints(cls, include_extras=True)
|
|
713
|
+
cls.__cachedTypeHintsWithExtras__ = hints
|
|
714
|
+
return hints
|
|
715
|
+
|
|
645
716
|
@classmethod
|
|
646
717
|
def tupleJsonSchema(cls) -> "JsonSchemaNodeTuple":
|
|
647
718
|
"""Tuple JSON Schema
|
|
@@ -43,7 +43,6 @@ from .SerialiseUtil import WKBElement
|
|
|
43
43
|
from .SerialiseUtil import convertFromWkbElement
|
|
44
44
|
from .SerialiseUtil import datetimeFromBson
|
|
45
45
|
from .SerialiseUtil import datetimeToBson
|
|
46
|
-
from .SerialiseUtil import datetimeToBsonMicroseconds
|
|
47
46
|
from .SerialiseUtil import requireAwareDatetime
|
|
48
47
|
from .Tuple import BsonDatetimeFormatE
|
|
49
48
|
from .Tuple import TupleField
|
|
@@ -65,24 +64,6 @@ class TupleBsonConverter(TupleDocumentConverterBase):
|
|
|
65
64
|
return tupleField.bsonDatetimeFormat
|
|
66
65
|
return None
|
|
67
66
|
|
|
68
|
-
@classmethod
|
|
69
|
-
def _utilDatetimeToBsonStorageValue(
|
|
70
|
-
cls,
|
|
71
|
-
value: datetime,
|
|
72
|
-
bsonDatetimeFormat: BsonDatetimeFormatE | None,
|
|
73
|
-
) -> datetime | int:
|
|
74
|
-
if bsonDatetimeFormat is None:
|
|
75
|
-
return datetimeToBson(value)
|
|
76
|
-
if bsonDatetimeFormat is BsonDatetimeFormatE.standardMsIntEpoch:
|
|
77
|
-
return datetimeToBson(value)
|
|
78
|
-
if bsonDatetimeFormat is BsonDatetimeFormatE.standardUsIntEpoch:
|
|
79
|
-
return datetimeToBsonMicroseconds(value)
|
|
80
|
-
if bsonDatetimeFormat is BsonDatetimeFormatE.standardMsBsonDate:
|
|
81
|
-
return requireAwareDatetime(value)
|
|
82
|
-
raise NotImplementedError(
|
|
83
|
-
f"Unsupported BsonDatetimeFormatE {bsonDatetimeFormat!r}"
|
|
84
|
-
)
|
|
85
|
-
|
|
86
67
|
@classmethod
|
|
87
68
|
def _utilCoerceIntOrFloatEpochToDatetime(
|
|
88
69
|
cls,
|
|
@@ -145,7 +126,7 @@ class TupleBsonConverter(TupleDocumentConverterBase):
|
|
|
145
126
|
|
|
146
127
|
def convert(
|
|
147
128
|
value: Any,
|
|
148
|
-
|
|
129
|
+
tupleField: TupleField | None = None,
|
|
149
130
|
) -> Any:
|
|
150
131
|
if isinstance(value, list):
|
|
151
132
|
return [convert(item) for item in value]
|
|
@@ -165,17 +146,18 @@ class TupleBsonConverter(TupleDocumentConverterBase):
|
|
|
165
146
|
return None
|
|
166
147
|
|
|
167
148
|
if isinstance(value, datetime):
|
|
168
|
-
|
|
149
|
+
if tupleField is None:
|
|
150
|
+
return datetimeToBson(value)
|
|
151
|
+
return tupleField.toBson(value)
|
|
169
152
|
|
|
170
153
|
return value
|
|
171
154
|
|
|
172
155
|
for shortName, normalName in tuple_.__shortFieldNamesMap__.items():
|
|
173
156
|
tupleField = getattr(type(tuple_), normalName, None)
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
fieldFormat = tupleField.bsonDatetimeFormat
|
|
157
|
+
if not isinstance(tupleField, TupleField):
|
|
158
|
+
tupleField = None
|
|
177
159
|
|
|
178
|
-
value = convert(getattr(tuple_, normalName),
|
|
160
|
+
value = convert(getattr(tuple_, normalName), tupleField)
|
|
179
161
|
if value is None and not includeNones:
|
|
180
162
|
continue
|
|
181
163
|
if value is False and not includeFalse:
|
|
@@ -250,6 +250,33 @@ class TupleBsonTest(unittest.TestCase):
|
|
|
250
250
|
)
|
|
251
251
|
self.assertEqual(deserialisedTuple.at, aware)
|
|
252
252
|
|
|
253
|
+
def testTupleFieldToBsonConvertsDatetimePerFormat(self) -> None:
|
|
254
|
+
aware = datetime(2020, 1, 2, 3, 4, 5, 678000, tzinfo=UTC)
|
|
255
|
+
self.assertEqual(
|
|
256
|
+
BsonDatetimeFormatTestTuple.defaultMsDate.toBson(aware),
|
|
257
|
+
datetimeToBson(aware),
|
|
258
|
+
)
|
|
259
|
+
self.assertEqual(
|
|
260
|
+
BsonDatetimeFormatTestTuple.microsecondDate.toBson(aware),
|
|
261
|
+
datetimeToBsonMicroseconds(aware),
|
|
262
|
+
)
|
|
263
|
+
self.assertEqual(
|
|
264
|
+
BsonDatetimeFormatTestTuple.bsonDateField.toBson(aware),
|
|
265
|
+
aware,
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
def testTupleFieldToBsonPassesThroughNonDatetimeValues(self) -> None:
|
|
269
|
+
field = BsonDatetimeFormatTestTuple.defaultMsDate
|
|
270
|
+
self.assertEqual(field.toBson("2026-10-23"), "2026-10-23")
|
|
271
|
+
self.assertEqual(field.toBson(1577934245678), 1577934245678)
|
|
272
|
+
self.assertIs(field.toBson(True), True)
|
|
273
|
+
self.assertIsNone(field.toBson(None))
|
|
274
|
+
|
|
275
|
+
def testTupleFieldToBsonRejectsNaiveDatetime(self) -> None:
|
|
276
|
+
naive = datetime(2020, 1, 2, 3, 4, 5, 678000)
|
|
277
|
+
with self.assertRaises(TypeError):
|
|
278
|
+
BsonDatetimeFormatTestTuple.defaultMsDate.toBson(naive)
|
|
279
|
+
|
|
253
280
|
|
|
254
281
|
class TupleBsonCollectingTest(unittest.TestCase):
|
|
255
282
|
def testSingleBadScalarCollectsErrorAndPopulatesOtherFields(self) -> None:
|
|
@@ -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)
|
|
@@ -43,7 +43,10 @@ VortexServer
|
|
|
43
43
|
"""
|
|
44
44
|
|
|
45
45
|
HEART_BEAT_PERIOD = 10.0
|
|
46
|
-
|
|
46
|
+
# Browser background-tab timer throttling (Chrome intensive wake-up throttling)
|
|
47
|
+
# can reduce a hidden tab's client heartbeats to one per minute; 75s gives one
|
|
48
|
+
# full missed-beat margin over that 60s cadence before the connection is closed.
|
|
49
|
+
HEART_BEAT_TIMEOUT = 75.0
|
|
47
50
|
|
|
48
51
|
|
|
49
52
|
class VortexServer(VortexABC):
|
|
@@ -362,7 +365,11 @@ class VortexSessionI(Interface):
|
|
|
362
365
|
@implementer(VortexSessionI)
|
|
363
366
|
class VortexSession(object):
|
|
364
367
|
def __init__(self, session):
|
|
365
|
-
session
|
|
368
|
+
# Never shorten a configured session timeout: the heartbeat floor
|
|
369
|
+
# applies only when the app (or Twisted default) allows less.
|
|
370
|
+
# Shortening here expires active operator sessions when browser
|
|
371
|
+
# background-tab timer throttling delays heartbeats past 60s.
|
|
372
|
+
session.sessionTimeout = max(session.sessionTimeout, HEART_BEAT_TIMEOUT)
|
|
366
373
|
self.connections = {}
|
|
367
374
|
|
|
368
375
|
|
vortexpy-4.3.7/LICENSE
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2016-2026 Flying Ion Pty Ltd
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
22
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{vortexpy-4.3.7 → vortexpy-4.3.10}/vortex/tuple_json_schema/tuple_json_schema_test_tuples.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|