From 1e3301c03793dda1671559f3b4dd4adeb3633ef1 Mon Sep 17 00:00:00 2001 From: Lena Garber Date: Mon, 14 Oct 2024 14:19:45 -0400 Subject: [PATCH 1/2] Add include_none_values ClassVar to JSONObject; apply to response-only structures --- linode_api4/objects/image.py | 2 ++ linode_api4/objects/lke.py | 6 ++++++ linode_api4/objects/serializable.py | 8 +++++++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/linode_api4/objects/image.py b/linode_api4/objects/image.py index c9ac43863..931ed4a31 100644 --- a/linode_api4/objects/image.py +++ b/linode_api4/objects/image.py @@ -24,6 +24,8 @@ class ImageRegion(JSONObject): The region and status of an image replica. """ + include_none_values = True + region: str = "" status: Optional[ReplicationStatus] = None diff --git a/linode_api4/objects/lke.py b/linode_api4/objects/lke.py index b0e628196..1c2ed3c1a 100644 --- a/linode_api4/objects/lke.py +++ b/linode_api4/objects/lke.py @@ -55,6 +55,8 @@ class LKENodePoolTaint(JSONObject): applied to a node pool. """ + include_none_values = True + key: Optional[str] = None value: Optional[str] = None effect: Optional[str] = None @@ -103,6 +105,8 @@ class LKEClusterControlPlaneACLAddresses(JSONObject): to access an LKE cluster's control plane. """ + include_none_values = True + ipv4: Optional[List[str]] = None ipv6: Optional[List[str]] = None @@ -116,6 +120,8 @@ class LKEClusterControlPlaneACL(JSONObject): NOTE: Control Plane ACLs may not currently be available to all users. """ + include_none_values = True + enabled: bool = False addresses: Optional[LKEClusterControlPlaneACLAddresses] = None diff --git a/linode_api4/objects/serializable.py b/linode_api4/objects/serializable.py index b0e7a2503..fea682f43 100644 --- a/linode_api4/objects/serializable.py +++ b/linode_api4/objects/serializable.py @@ -58,6 +58,12 @@ class JSONObject(metaclass=JSONFilterableMetaclass): ) """ + include_none_values: ClassVar[bool] = False + """ + If true, all None values for this class will be explicitly included in + the serialized output for instance of this class. + """ + always_include: ClassVar[Set[str]] = {} """ A set of keys corresponding to fields that should always be @@ -169,7 +175,7 @@ def should_include(key: str, value: Any) -> bool: Returns whether the given key/value pair should be included in the resulting dict. """ - if key in cls.always_include: + if cls.include_none_values or key in cls.always_include: return True hint = type_hints.get(key) From a725a5eeb7d924ad158a6f9eb802807a407f7232 Mon Sep 17 00:00:00 2001 From: Lena Garber Date: Mon, 14 Oct 2024 14:23:50 -0400 Subject: [PATCH 2/2] Add unit test case --- test/unit/objects/serializable_test.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test/unit/objects/serializable_test.py b/test/unit/objects/serializable_test.py index 579417e1c..a15f108b4 100644 --- a/test/unit/objects/serializable_test.py +++ b/test/unit/objects/serializable_test.py @@ -26,3 +26,24 @@ class Foo(JSONObject): assert foo["foo"] == "test" assert foo["bar"] == "test2" assert foo["baz"] == "test3" + + def test_serialize_optional_include_None(self): + @dataclass + class Foo(JSONObject): + include_none_values = True + + foo: Optional[str] = None + bar: Optional[str] = None + baz: str = None + + foo = Foo().dict + + assert foo["foo"] is None + assert foo["bar"] is None + assert foo["baz"] is None + + foo = Foo(foo="test", bar="test2", baz="test3").dict + + assert foo["foo"] == "test" + assert foo["bar"] == "test2" + assert foo["baz"] == "test3"