From cb9d6ade4f97bf49f49b87bcee5fc5923d45cd6c Mon Sep 17 00:00:00 2001 From: OMGeeky Date: Fri, 17 May 2024 23:50:04 +0200 Subject: [PATCH] Use DictObject in tests (copied version from `mako-render`) --- src/generator/lib/__tests__/__init__.py | 192 ++++++++++++++++++++ src/generator/lib/__tests__/context_test.py | 41 +++-- 2 files changed, 213 insertions(+), 20 deletions(-) diff --git a/src/generator/lib/__tests__/__init__.py b/src/generator/lib/__tests__/__init__.py index e69de29bb2..d7395a36cb 100644 --- a/src/generator/lib/__tests__/__init__.py +++ b/src/generator/lib/__tests__/__init__.py @@ -0,0 +1,192 @@ +from copy import deepcopy +import sys + +# COPY FROM `mako-render` TO CONVERT DATA FOR TESTS + +# From https://github.com/Byron/bcore +class DictObject(object): + + """An object which wraps a dictionary to allow object.key access. + If the source dictionary doesn't contain any sub-dictionaries, the input + dict will be referenced. Otherwise it will be copied. + + An attribute error is raised if a value is not accessible. + + Please note that you cannot access dict keys which are not valid attribute names. + """ + + _default_dict = dict() + _unpackable_types = (dict, tuple, list) + + def __init__(self, indict=_default_dict): + """Initialize this instance from an input dictionary. If it contains other dictionaries, those will + trigger their parent dictionaries to be copied, as they will be used as DictObject themselves and + placed in the copy accordingly. + NOTE: other DictObjects are used by reference. Generally, this type tries to perform the least + amount of copying possible.""" + if indict is self._default_dict: + return + # end handle default instantiation, which makes us empty + if isinstance(indict, DictObject): + self.__dict__ = indict.__dict__ + return + # END handle special case, be a reference + dct = indict + for key, val in dct.items(): + if isinstance(val, self._unpackable_types): + dct = None + break + # END for each key-value pair + + if dct is None: + dct = dict(indict) + + def unpack(val): + """unpack helper""" + if isinstance(val, dict): + val = DictObject(val) + elif isinstance(val, (tuple, list)): + val = type(val)(unpack(item) for item in val) + return val + # END unpack + for key, val in dct.items(): + dct[key] = unpack(val) + # END for each k,v pair + # END handle recursive copy + self.__dict__ = dct + + def __str__(self): + return str(self.__dict__) + + def __repr__(self): + return repr(self.__dict__) + + def __getitem__(self, name): + try: + return getattr(self, name) + except AttributeError: + raise KeyError(name) + # end convert exception + + def __setitem__(self, name, value): + setattr(self, name, value) + + def __contains__(self, name): + return name in self.__dict__ + + def __len__(self): + return len(self.__dict__) + + def __iter__(self): + return iter(self.__dict__) + + def __eq__(self, other): + """Compares a possibly expensive comparison""" + if isinstance(other, DictObject): + # EXPENSIVE ! + return self.to_dict() == other.to_dict() + elif isinstance(other, dict): + return self.to_dict() == other + # end handle type of other + return self is other + + def update(self, other, **kwargs): + """Similar to dict.update""" + items = other + if hasattr(other, 'keys'): + items = other.items() + for item_list in (items, kwargs.items()): + for k, v in item_list: + setattr(self, k, v) + # end for each item list + + def to_dict(self, recursive=False): + """@return ourselves as normal dict + @param recursive if True, a recursive copy will be returned if required.""" + if recursive: + def obtain_needs_copy(value): + """figure out if a copy is required""" + if isinstance(value, DictObject): + return True + if isinstance(value, (tuple, list, set)): + for item in value: + if obtain_needs_copy(item): + return True + # end check needs copy + # end for each item in value + # end if instance is iterable + return False + # end check needs copy + + def unpack(val): + """unpack val recursively and copy it gently""" + if isinstance(val, DictObject): + val = val.to_dict(recursive) + elif isinstance(val, (tuple, list, set)): + val = type(val)(unpack(item) for item in val) + # end handle type resolution + return val + # end unpack + + needs_copy = False + for value in self.__dict__.values(): + if obtain_needs_copy(value): + needs_copy = True + break + # end check value + # END for each value + + if needs_copy: + new_dict = dict() + for key, val in self.__dict__.items(): + new_dict[key] = unpack(val) + # END for each key, value pair + return new_dict + # else: + # just fall through and return ourselves as dictionary + + # END handle recursion + return self.__dict__ + + def copy(self): + """@return a (deep) copy of self""" + return type(self)(self.to_dict()) + + def clone(self): + """@return a deep copy of this dict. This onyl means that the key-sets are independent. However, the + values are still shared, which matters in case of lists for instance""" + return type(self)(deepcopy(self.to_dict(recursive=True))) + + def inversed_dict(self): + """@return new dictionary which uses this dicts keys as values, and values as keys + @note duplicate values will result in just a single key, effectively drupping items. + Use this only if you have unique key-value pairs""" + return dict(list(zip(list(self.__dict__.values()), list(self.__dict__.keys())))) + + def get(self, name, default=None): + """as dict.get""" + return self.__dict__.get(name, default) + + def keys(self): + """as dict.keys""" + return list(self.__dict__.keys()) + + def values(self): + """as dict.values""" + return list(self.__dict__.values()) + + def items(self): + """as dict.items""" + return list(self.__dict__.items()) + + def _items(self): + """as dict.items, avoiding name clashes""" + return list(self.__dict__.items()) + + def pop(self, key, default=sys): + """as dict.pop""" + if default is sys: + return self.__dict__.pop(key) + else: + return self.__dict__.pop(key, default) + # end assure semantics are kept diff --git a/src/generator/lib/__tests__/context_test.py b/src/generator/lib/__tests__/context_test.py index 37fb9b2fc4..f9271e4fce 100644 --- a/src/generator/lib/__tests__/context_test.py +++ b/src/generator/lib/__tests__/context_test.py @@ -3,6 +3,7 @@ import unittest from pprint import pprint from generator.lib.util import new_context +from . import DictObject from .test_data.discovery_document import DISCOVERY_DOC @@ -11,7 +12,7 @@ class ContextTest(unittest.TestCase): self.discovery_doc = json.loads(DISCOVERY_DOC) def test_sta_map(self): - expected = { + expected = DictObject({ "AddEnrichmentToAlbumRequest": { "photoslibrary.albums.addEnrichment": ["request"] }, @@ -59,16 +60,16 @@ class ContextTest(unittest.TestCase): "photoslibrary.sharedAlbums.join": [], "photoslibrary.sharedAlbums.list": [], }, - } + }) - schemas = self.discovery_doc["schemas"] - resources = self.discovery_doc["resources"] + schemas = DictObject(self.discovery_doc["schemas"]) + resources = DictObject(self.discovery_doc["resources"]) actual = new_context(schemas, resources).sta_map self.assertEqual(actual, expected) def test_fqan_map(self): - expected = { + expected = DictObject({ "photoslibrary.mediaItems.batchCreate": { "flatPath": "v1/mediaItems:batchCreate", "path": "v1/mediaItems:batchCreate", @@ -345,36 +346,36 @@ class ContextTest(unittest.TestCase): "path": "v1/albums/{+albumId}:share", "id": "photoslibrary.albums.share", }, - } + }) - schemas = self.discovery_doc["schemas"] - resources = self.discovery_doc["resources"] + schemas = DictObject(self.discovery_doc["schemas"]) + resources = DictObject(self.discovery_doc["resources"]) actual = new_context(schemas, resources).fqan_map self.assertEqual(actual, expected) def test_rta_map(self): - expected = { + expected = DictObject({ "mediaItems": ["batchCreate", "search", "list", "get"], "sharedAlbums": ["get", "list", "join"], "albums": ["list", "get", "addEnrichment", "create", "share"], - } + }) - schemas = self.discovery_doc["schemas"] - resources = self.discovery_doc["resources"] + schemas = DictObject(self.discovery_doc["schemas"]) + resources = DictObject(self.discovery_doc["resources"]) actual = new_context(schemas, resources).rta_map self.assertEqual(actual, expected) def test_rtc_map(self): - expected = { + expected = DictObject({ "mediaItems": "photoslibrary", "sharedAlbums": "photoslibrary", "albums": "photoslibrary", - } + }) - schemas = self.discovery_doc["schemas"] - resources = self.discovery_doc["resources"] + schemas = DictObject(self.discovery_doc["schemas"]) + resources = DictObject(self.discovery_doc["resources"]) actual = new_context(schemas, resources).rtc_map with open("actual.json", "w") as aj: @@ -382,7 +383,7 @@ class ContextTest(unittest.TestCase): self.assertEqual(actual, expected) def test_schemas(self): - expected = { + expected = DictObject({ "EnrichmentItem": { "description": "An enrichment item.", "type": "object", @@ -1360,10 +1361,10 @@ class ContextTest(unittest.TestCase): "used_by": ["MediaMetadata"], "parents": [], }, - } + }) - schemas = self.discovery_doc["schemas"] - resources = self.discovery_doc["resources"] + schemas = DictObject(self.discovery_doc["schemas"]) + resources = DictObject(self.discovery_doc["resources"]) actual = new_context(schemas, resources).schemas self.assertEqual(actual, expected)