vortexpy 4.3.4__tar.gz → 4.3.5__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.4 → vortexpy-4.3.5}/PKG-INFO +1 -1
- {vortexpy-4.3.4 → vortexpy-4.3.5}/pyproject.toml +1 -1
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/TupleBson_test.py +24 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/TupleDocumentConverter.py +121 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/TupleRestful_test.py +35 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/__init__.py +1 -1
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/handler/TupleDataObservableProxyHandler.py +4 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/tuple_test_fixtures.py +8 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortexpy.egg-info/PKG-INFO +1 -1
- {vortexpy-4.3.4 → vortexpy-4.3.5}/README.md +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/setup.cfg +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/DataWrapTuple.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/DeferUtil.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/DeferUtil_test.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/Jsonable.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/MtlsAuthorizedClientRegistry.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/MtlsAuthorizedClientRegistry_test.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/MtlsServerSslOptions.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/Payload.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/PayloadEndpoint.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/PayloadEndpointTest.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/PayloadEnvelope.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/PayloadFilterKeys.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/PayloadIO.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/PayloadPriority.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/PayloadResponse.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/PayloadResponseTest.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/PayloadResponse_test.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/PayloadTest.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/PerformTestActionTuple.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/SerialiseUtil.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/SerialiseUtil_test.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/TestTuple.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/Tuple.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/TupleAction.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/TupleBson.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/TupleDataForTest.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/TuplePerfTest.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/TuplePolymorphicTupleTypeFieldArgTest.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/TupleRestful.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/TupleSelector.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/TupleTest.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/VortexABC.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/VortexAccessControlContextABC.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/VortexAuthenticatorABC.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/VortexClientHttp.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/VortexClientHttpTest.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/VortexClientTcp.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/VortexClientWebsocketFactory.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/VortexConnectionABC.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/VortexEndpointPermission.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/VortexFactory.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/VortexFactory_test.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/VortexPayloadProtocol.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/VortexPermission.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/VortexPermissionError.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/VortexServer.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/VortexServerConnection.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/VortexServerConnection_test.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/VortexServerHttpResource.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/VortexServerTcp.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/VortexServerWebsocket.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/VortexSession.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/VortexSession_test.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/VortexTcpMemoryLeakPayloadTest.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/VortexTcpMemoryLeakRawTest.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/VortexTest.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/VortexUtil.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/VortexWritePushProducer.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/data_loader/TupleDataLiveLockManager.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/data_loader/TupleDataLoader.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/data_loader/TupleDataLoaderDelegate.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/data_loader/TupleDataLoaderTupleABC.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/data_loader/TupleDataLoaderTuples.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/data_loader/__init__.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/event_bus/TupleChangeEventABC.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/event_bus/TupleChangeEventBus.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/event_bus/TupleChangeEventBusObserverABC.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/event_bus/__init__.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/handler/TupleActionProcessor.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/handler/TupleActionProcessorProxy.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/handler/TupleActionProcessorTestHandler.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/handler/TupleActionProcessor_shutdown_test.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/handler/TupleActionPushOffline.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/handler/TupleActionPushOffline_test.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/handler/TupleDataAccessGranterABC.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/handler/TupleDataActionClient.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/handler/TupleDataObservableCache.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/handler/TupleDataObservableHandler.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/handler/TupleDataObservableHandler_test.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/handler/TupleDataObservableTestHandler.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/handler/TupleDataObserverClient.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/handler/TupleDataProviderABC.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/handler/VortexJSTupleLoaderTestHandler.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/handler/__init__.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/mypy_plugin.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/payload_many_tuples_benchmark_test.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/py.typed +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/restful/GzippedDataHttpClient.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/restful/GzippedDataHttpClientTest.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/restful/GzippedPayloadHttpClient.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/restful/RestfulHttpClient.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/restful/RestfulResource.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/restful/__init__.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/restful/restful_http_client_test.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/restful/restful_resource_test.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/rpc/RPC.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/rpc/RPCTest.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/rpc/RPC_test.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/rpc/__init__.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/sqla_orm/OrmCrudHandler.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/sqla_orm/TupleProviderForDb.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/sqla_orm/VortexTupleJson.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/sqla_orm/__init__.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/storage/TupleActionStorageSqlite.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/storage/TupleStorageSqlite.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/storage/__init__.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/tuple_json_schema/__init__.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/tuple_json_schema/constants.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/tuple_json_schema/json_schema_node_tuple.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/tuple_json_schema/tuple_json_schema_builder.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/tuple_json_schema/tuple_json_schema_test.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/tuple_json_schema/tuple_json_schema_test_tuples.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/twisted_runtime_metrics.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortex/twisted_runtime_metrics_test.py +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortexpy.egg-info/SOURCES.txt +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortexpy.egg-info/dependency_links.txt +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortexpy.egg-info/entry_points.txt +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/vortexpy.egg-info/requires.txt +0 -0
- {vortexpy-4.3.4 → vortexpy-4.3.5}/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.5"
|
|
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."
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
# See LICENSE for the full license text.
|
|
3
3
|
|
|
4
4
|
from datetime import UTC, datetime
|
|
5
|
+
from unittest.mock import patch
|
|
5
6
|
|
|
6
7
|
from ddt import data, ddt
|
|
7
8
|
import unittest
|
|
@@ -12,6 +13,7 @@ from vortex.tuple_test_fixtures import (
|
|
|
12
13
|
BsonEnumTestTuple,
|
|
13
14
|
BsonObjectTypeE,
|
|
14
15
|
BsonOptionalDatetimeTestTuple,
|
|
16
|
+
BsonOptionalFloatTestTuple,
|
|
15
17
|
BsonPriorityE,
|
|
16
18
|
BsonTestTuple,
|
|
17
19
|
makeBsonTestTuple,
|
|
@@ -49,6 +51,28 @@ class TupleBsonTest(unittest.TestCase):
|
|
|
49
51
|
deserialisedTuple = self._testSerialisation(origTuple)
|
|
50
52
|
self.assertEqual(TupleHash(origTuple), TupleHash(deserialisedTuple))
|
|
51
53
|
|
|
54
|
+
def testBsonIntegerParsesAsOptionalFloatField(self) -> None:
|
|
55
|
+
deserialisedTuple = Tuple.bsonJsonDictToTupleWithValidation(
|
|
56
|
+
dict(requiredFloat=151, optionalFloat=0),
|
|
57
|
+
BsonOptionalFloatTestTuple,
|
|
58
|
+
)
|
|
59
|
+
self.assertEqual(deserialisedTuple.requiredFloat, 151.0)
|
|
60
|
+
self.assertEqual(deserialisedTuple.optionalFloat, 0.0)
|
|
61
|
+
|
|
62
|
+
def testBsonFractionalDoubleParsesAsIntFieldWithWarning(self) -> None:
|
|
63
|
+
bsonDict = validBsonDict()
|
|
64
|
+
bsonDict["aInt"] = 151.7
|
|
65
|
+
with patch(
|
|
66
|
+
"vortex.TupleDocumentConverter.logger.warning"
|
|
67
|
+
) as mockWarning:
|
|
68
|
+
deserialisedTuple = Tuple.bsonJsonDictToTupleWithValidation(
|
|
69
|
+
bsonDict, BsonTestTuple
|
|
70
|
+
)
|
|
71
|
+
self.assertEqual(deserialisedTuple.aInt, 151)
|
|
72
|
+
mockWarning.assert_called_once()
|
|
73
|
+
warningMessage = mockWarning.call_args[0][0]
|
|
74
|
+
self.assertIn("lost precision", warningMessage)
|
|
75
|
+
|
|
52
76
|
def testDatetimeRoundTrip(self) -> None:
|
|
53
77
|
origTuple = BsonOptionalDatetimeTestTuple()
|
|
54
78
|
origTuple.requiredDate = datetime(2020, 1, 2, 3, 4, 5, 678000, tzinfo=UTC)
|
|
@@ -15,6 +15,7 @@ Subclasses supply datetime and plain-dict coercion at the document boundary.
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
17
|
import enum
|
|
18
|
+
import logging
|
|
18
19
|
from typing import TYPE_CHECKING, Annotated, Any, NamedTuple, Optional, Type
|
|
19
20
|
|
|
20
21
|
from typing import Union, get_args
|
|
@@ -29,6 +30,8 @@ from .Tuple import TupleFieldValidatorABC
|
|
|
29
30
|
if TYPE_CHECKING:
|
|
30
31
|
from .Tuple import Tuple
|
|
31
32
|
|
|
33
|
+
logger = logging.getLogger(__name__)
|
|
34
|
+
|
|
32
35
|
_UNSET = object()
|
|
33
36
|
_ROOT_PATH = "$"
|
|
34
37
|
|
|
@@ -287,6 +290,90 @@ class TupleDocumentConverterBase:
|
|
|
287
290
|
|
|
288
291
|
return value, None
|
|
289
292
|
|
|
293
|
+
@classmethod
|
|
294
|
+
def _scalarTypeFromHint(cls, typeHint: Any, scalarType: type) -> bool:
|
|
295
|
+
"""Return whether typeHint is ``scalarType`` or ``scalarType | None`` only."""
|
|
296
|
+
if typeHint is scalarType:
|
|
297
|
+
return True
|
|
298
|
+
|
|
299
|
+
unionArgs = get_args(typeHint)
|
|
300
|
+
if not unionArgs:
|
|
301
|
+
return False
|
|
302
|
+
|
|
303
|
+
nonNoneArgs = [
|
|
304
|
+
unionArg for unionArg in unionArgs if unionArg is not type(None)
|
|
305
|
+
]
|
|
306
|
+
return len(nonNoneArgs) == 1 and nonNoneArgs[0] is scalarType
|
|
307
|
+
|
|
308
|
+
@classmethod
|
|
309
|
+
def _floatTypeFromHint(cls, typeHint: Any) -> bool:
|
|
310
|
+
"""Return whether typeHint accepts float (including ``float | None``)."""
|
|
311
|
+
return cls._scalarTypeFromHint(typeHint, float)
|
|
312
|
+
|
|
313
|
+
@classmethod
|
|
314
|
+
def _tryParseFloatFieldValue(
|
|
315
|
+
cls,
|
|
316
|
+
field_path: str,
|
|
317
|
+
fieldName: str,
|
|
318
|
+
TupleClass: Type[Tuple],
|
|
319
|
+
typeHint: Any,
|
|
320
|
+
value: Any,
|
|
321
|
+
) -> tuple[Any, TupleValidationError | None]:
|
|
322
|
+
"""Parse JSON integer tokens as ``float`` when the field type is ``float``."""
|
|
323
|
+
if value is None:
|
|
324
|
+
return value, None
|
|
325
|
+
|
|
326
|
+
if not cls._floatTypeFromHint(typeHint):
|
|
327
|
+
return value, None
|
|
328
|
+
|
|
329
|
+
if isinstance(value, float):
|
|
330
|
+
return value, None
|
|
331
|
+
|
|
332
|
+
if isinstance(value, int) and not isinstance(value, bool):
|
|
333
|
+
return float(value), None
|
|
334
|
+
|
|
335
|
+
return value, None
|
|
336
|
+
|
|
337
|
+
@classmethod
|
|
338
|
+
def _intTypeFromHint(cls, typeHint: Any) -> bool:
|
|
339
|
+
"""Return whether typeHint accepts int (including ``int | None`` only)."""
|
|
340
|
+
return cls._scalarTypeFromHint(typeHint, int)
|
|
341
|
+
|
|
342
|
+
@classmethod
|
|
343
|
+
def _tryParseIntFieldValue(
|
|
344
|
+
cls,
|
|
345
|
+
field_path: str,
|
|
346
|
+
fieldName: str,
|
|
347
|
+
TupleClass: Type[Tuple],
|
|
348
|
+
typeHint: Any,
|
|
349
|
+
value: Any,
|
|
350
|
+
) -> tuple[Any, TupleValidationError | None]:
|
|
351
|
+
"""Parse JSON float tokens as ``int`` when the field type is ``int``."""
|
|
352
|
+
if value is None:
|
|
353
|
+
return value, None
|
|
354
|
+
|
|
355
|
+
if not cls._intTypeFromHint(typeHint):
|
|
356
|
+
return value, None
|
|
357
|
+
|
|
358
|
+
if isinstance(value, int) and not isinstance(value, bool):
|
|
359
|
+
return value, None
|
|
360
|
+
|
|
361
|
+
if isinstance(value, float):
|
|
362
|
+
parsedInt = int(value)
|
|
363
|
+
if not value.is_integer():
|
|
364
|
+
logger.warning(
|
|
365
|
+
"At %s: parsing %s field %s lost precision converting "
|
|
366
|
+
"float %r to int %s",
|
|
367
|
+
field_path,
|
|
368
|
+
TupleClass.__name__,
|
|
369
|
+
fieldName,
|
|
370
|
+
value,
|
|
371
|
+
parsedInt,
|
|
372
|
+
)
|
|
373
|
+
return parsedInt, None
|
|
374
|
+
|
|
375
|
+
return value, None
|
|
376
|
+
|
|
290
377
|
@classmethod
|
|
291
378
|
def _nestedTupleTypeFromHint(cls, typeHint: Any) -> Any | None:
|
|
292
379
|
"""Return nested tuple class for a field hint, including ``X | None`` unions."""
|
|
@@ -413,6 +500,26 @@ class TupleDocumentConverterBase:
|
|
|
413
500
|
if enumError:
|
|
414
501
|
errors.append(enumError)
|
|
415
502
|
continue
|
|
503
|
+
item, floatError = cls._tryParseFloatFieldValue(
|
|
504
|
+
item_path,
|
|
505
|
+
fieldName,
|
|
506
|
+
TupleClass,
|
|
507
|
+
listItemTypeHint,
|
|
508
|
+
item,
|
|
509
|
+
)
|
|
510
|
+
if floatError:
|
|
511
|
+
errors.append(floatError)
|
|
512
|
+
continue
|
|
513
|
+
item, intError = cls._tryParseIntFieldValue(
|
|
514
|
+
item_path,
|
|
515
|
+
fieldName,
|
|
516
|
+
TupleClass,
|
|
517
|
+
listItemTypeHint,
|
|
518
|
+
item,
|
|
519
|
+
)
|
|
520
|
+
if intError:
|
|
521
|
+
errors.append(intError)
|
|
522
|
+
continue
|
|
416
523
|
itemError = cls._checkType(
|
|
417
524
|
TupleClass,
|
|
418
525
|
item_path,
|
|
@@ -578,6 +685,20 @@ class TupleDocumentConverterBase:
|
|
|
578
685
|
errors.append(enumError)
|
|
579
686
|
return _ConvertResult(_UNSET, errors)
|
|
580
687
|
|
|
688
|
+
value, floatError = cls._tryParseFloatFieldValue(
|
|
689
|
+
field_path, fieldName, TupleClass, typeHint, value
|
|
690
|
+
)
|
|
691
|
+
if floatError:
|
|
692
|
+
errors.append(floatError)
|
|
693
|
+
return _ConvertResult(_UNSET, errors)
|
|
694
|
+
|
|
695
|
+
value, intError = cls._tryParseIntFieldValue(
|
|
696
|
+
field_path, fieldName, TupleClass, typeHint, value
|
|
697
|
+
)
|
|
698
|
+
if intError:
|
|
699
|
+
errors.append(intError)
|
|
700
|
+
return _ConvertResult(_UNSET, errors)
|
|
701
|
+
|
|
581
702
|
typeError = cls._checkType(
|
|
582
703
|
TupleClass, field_path, fieldName, typeHint, value, validator
|
|
583
704
|
)
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import json
|
|
5
5
|
from datetime import UTC, datetime
|
|
6
|
+
from unittest.mock import patch
|
|
6
7
|
|
|
7
8
|
from ddt import data, ddt
|
|
8
9
|
import unittest
|
|
@@ -102,6 +103,40 @@ class TupleRestfulTest(unittest.TestCase):
|
|
|
102
103
|
self.assertNotIn("aBoolFalse", jsonDict)
|
|
103
104
|
self.assertIn("aBoolTrue", jsonDict)
|
|
104
105
|
|
|
106
|
+
def testRestfulIntegerJsonParsesAsFloatField(self) -> None:
|
|
107
|
+
jsonDict = _validRestfulJsonDict()
|
|
108
|
+
jsonDict["aFloat"] = 151
|
|
109
|
+
deserialisedTuple = Tuple.restfulJsonDictToTupleWithValidation(
|
|
110
|
+
jsonDict, RestfulTestTuple
|
|
111
|
+
)
|
|
112
|
+
self.assertEqual(deserialisedTuple.aFloat, 151.0)
|
|
113
|
+
|
|
114
|
+
def testRestfulWholeFloatJsonParsesAsIntField(self) -> None:
|
|
115
|
+
jsonDict = _validRestfulJsonDict()
|
|
116
|
+
jsonDict["aInt"] = 151.0
|
|
117
|
+
with patch(
|
|
118
|
+
"vortex.TupleDocumentConverter.logger.warning"
|
|
119
|
+
) as mockWarning:
|
|
120
|
+
deserialisedTuple = Tuple.restfulJsonDictToTupleWithValidation(
|
|
121
|
+
jsonDict, RestfulTestTuple
|
|
122
|
+
)
|
|
123
|
+
self.assertEqual(deserialisedTuple.aInt, 151)
|
|
124
|
+
mockWarning.assert_not_called()
|
|
125
|
+
|
|
126
|
+
def testRestfulFractionalFloatJsonParsesAsIntFieldWithWarning(self) -> None:
|
|
127
|
+
jsonDict = _validRestfulJsonDict()
|
|
128
|
+
jsonDict["aInt"] = 151.7
|
|
129
|
+
with patch(
|
|
130
|
+
"vortex.TupleDocumentConverter.logger.warning"
|
|
131
|
+
) as mockWarning:
|
|
132
|
+
deserialisedTuple = Tuple.restfulJsonDictToTupleWithValidation(
|
|
133
|
+
jsonDict, RestfulTestTuple
|
|
134
|
+
)
|
|
135
|
+
self.assertEqual(deserialisedTuple.aInt, 151)
|
|
136
|
+
mockWarning.assert_called_once()
|
|
137
|
+
warningMessage = mockWarning.call_args[0][0]
|
|
138
|
+
self.assertIn("lost precision", warningMessage)
|
|
139
|
+
|
|
105
140
|
def testRestfulConverterClassMethodsMatchTupleDelegates(self) -> None:
|
|
106
141
|
origTuple = makeRestfulTestTuple()
|
|
107
142
|
viaTuple = origTuple.tupleToRestfulJsonDict()
|
|
@@ -174,6 +174,7 @@ class TupleDataObservableProxyHandler(TupleDataObservableCache):
|
|
|
174
174
|
payloadEnvelope,
|
|
175
175
|
vortexUuid,
|
|
176
176
|
sendResponse,
|
|
177
|
+
vortexName,
|
|
177
178
|
vortexSession=vortexSession,
|
|
178
179
|
)
|
|
179
180
|
if d:
|
|
@@ -186,6 +187,7 @@ class TupleDataObservableProxyHandler(TupleDataObservableCache):
|
|
|
186
187
|
payloadEnvelope,
|
|
187
188
|
vortexUuid,
|
|
188
189
|
sendResponse,
|
|
190
|
+
vortexName,
|
|
189
191
|
vortexSession=vortexSession,
|
|
190
192
|
)
|
|
191
193
|
if d:
|
|
@@ -199,6 +201,7 @@ class TupleDataObservableProxyHandler(TupleDataObservableCache):
|
|
|
199
201
|
payloadEnvelope: PayloadEnvelope,
|
|
200
202
|
vortexUuid: str,
|
|
201
203
|
sendResponse: SendVortexMsgResponseCallable,
|
|
204
|
+
vortexName: str,
|
|
202
205
|
vortexSession=None,
|
|
203
206
|
):
|
|
204
207
|
|
|
@@ -210,6 +213,7 @@ class TupleDataObservableProxyHandler(TupleDataObservableCache):
|
|
|
210
213
|
payloadEnvelope=payloadEnvelope,
|
|
211
214
|
vortexUuid=vortexUuid,
|
|
212
215
|
sendResponse=sendResponse,
|
|
216
|
+
vortexName=vortexName,
|
|
213
217
|
vortexSession=vortexSession,
|
|
214
218
|
)
|
|
215
219
|
|
|
@@ -149,6 +149,14 @@ class BsonEnumTestTuple(Tuple):
|
|
|
149
149
|
priorities: List[BsonPriorityE] = TupleField()
|
|
150
150
|
|
|
151
151
|
|
|
152
|
+
@addTupleType
|
|
153
|
+
class BsonOptionalFloatTestTuple(Tuple):
|
|
154
|
+
__tupleType__ = "flyingion.vortex.BsonOptionalFloatTestTuple"
|
|
155
|
+
|
|
156
|
+
requiredFloat: float = TupleField()
|
|
157
|
+
optionalFloat: float | None = TupleField()
|
|
158
|
+
|
|
159
|
+
|
|
152
160
|
@addTupleType
|
|
153
161
|
class BsonOptionalDatetimeTestTuple(Tuple):
|
|
154
162
|
__tupleType__ = "flyingion.vortex.BsonOptionalDatetimeTestTuple"
|
|
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
|