From 7cf4eee0932a56e8be8dc5903dfa7950ba8171b1 Mon Sep 17 00:00:00 2001 From: Jeroen van der Heijden Date: Fri, 12 Jun 2020 13:55:21 +0200 Subject: [PATCH 1/3] Better enum support --- sample_lego.py | 8 +++++-- thingsdb/client/client.py | 1 + thingsdb/model/__init__.py | 3 ++- thingsdb/model/enum.py | 24 +++++++++++++++------ thingsdb/model/{member.py => enummember.py} | 8 ++++++- thingsdb/util/convert.py | 2 +- thingsdb/util/fmt.py | 2 -- thingsdb/version.py | 2 +- 8 files changed, 35 insertions(+), 15 deletions(-) rename thingsdb/model/{member.py => enummember.py} (66%) diff --git a/sample_lego.py b/sample_lego.py index abf7df8..625a091 100644 --- a/sample_lego.py +++ b/sample_lego.py @@ -1,6 +1,6 @@ import asyncio from thingsdb.client import Client -from thingsdb.model import Collection, Thing, ThingStrict, Enum +from thingsdb.model import Collection, Thing, ThingStrict, Enum, EnumMember from thingsdb.util import event @@ -53,13 +53,17 @@ async def example(): # ... now the collection will be watched for 100 seconds while True: await asyncio.sleep(3) + print('Color:', Color.GREEN is lego.bricks[0].color) + print('Is Enum', isinstance(Color.GREEN, Enum)) + print('Is EnumMember', isinstance(Color.GREEN, EnumMember)) + print('Is Color', isinstance(Color.GREEN, Color)) if lego and lego.bricks: brick = lego.bricks[0] await brick.emit('new-color', 'RED') break await lego.query('.bricks.push(Brick{});') - await asyncio.sleep(300) + await asyncio.sleep(30) finally: client.close() diff --git a/thingsdb/client/client.py b/thingsdb/client/client.py index a7294bb..9c95aba 100644 --- a/thingsdb/client/client.py +++ b/thingsdb/client/client.py @@ -13,6 +13,7 @@ from ..exceptions import ForbiddenError + _WATCH_MISSING = \ 'auto reconnect cannot act on node changes since `WATCH` privileges on ' \ 'the `@node` scope are missing' diff --git a/thingsdb/model/__init__.py b/thingsdb/model/__init__.py index 76bc6dc..a6c83cb 100644 --- a/thingsdb/model/__init__.py +++ b/thingsdb/model/__init__.py @@ -1,3 +1,4 @@ -from .collection import Collection from .enum import Enum +from .enummember import EnumMember +from .collection import Collection from .thing import Thing, ThingStrict, ThingHash diff --git a/thingsdb/model/enum.py b/thingsdb/model/enum.py index d4216f7..005c3fe 100644 --- a/thingsdb/model/enum.py +++ b/thingsdb/model/enum.py @@ -1,4 +1,4 @@ -from .member import Member +from .enummember import EnumMember _enums_lookup = {} # enums lookup by name @@ -7,14 +7,18 @@ class Enum: _visited = 0 # For build, 0=not visited, 1=new_type, 2=set_type, 3=build def __init_subclass__(cls, **kwargs): + if issubclass(cls, EnumMember): + return + cls._name = getattr(cls, '__NAME__', cls.__name__) cls._id = None + cls._memberclass = type(f'{cls._name}Member', (EnumMember, cls), {}) # upgrade attributes to member instances for k, v in cls.__dict__.items(): if k.startswith('_'): continue - setattr(cls, k, Member(cls._name, k, v)) + setattr(cls, k, cls._memberclass(cls._name, k, v)) # register for lookup by name _enums_lookup[cls._name] = cls @@ -22,9 +26,11 @@ def __init_subclass__(cls, **kwargs): @staticmethod def _update_enum(enums, data, convert): name = data['name'] - members = [Member(name, k, convert(v)) for k, v in data['members']] - enum = _enums_lookup.get(name) + cls = EnumMember if enum is None else enum._memberclass + + members = [cls(name, k, convert(v)) for k, v in data['members']] + if enum is not None: enum._id = data['enum_id'] for member in members: @@ -36,10 +42,12 @@ def _update_enum(enums, data, convert): def _upd_enum_add(enums, data, convert): members = enums[data['enum_id']] name = members[0]._enum_name - member = Member(name, data['name'], convert(data['value'])) + enum = _enums_lookup.get(name) + cls = EnumMember if enum is None else enum._memberclass + + member = cls(name, data['name'], convert(data['value'])) members.append(member) - enum = _enums_lookup.get(name) if enum is not None: setattr(enum, member.name, member) @@ -93,7 +101,9 @@ async def _new_type(cls, client, collection): if cls._visited > 0: return cls._visited += 1 - members = (m for m in cls.__dict__.values() if isinstance(m, Member)) + members = ( + m for m in cls.__dict__.values() + if isinstance(m, EnumMember)) query = f''' set_enum('{cls._name}', {{ {', '.join(f'{m.name}: {m.value!r}' for m in members)} diff --git a/thingsdb/model/member.py b/thingsdb/model/enummember.py similarity index 66% rename from thingsdb/model/member.py rename to thingsdb/model/enummember.py index ce640be..60eafa9 100644 --- a/thingsdb/model/member.py +++ b/thingsdb/model/enummember.py @@ -1,4 +1,4 @@ -class Member: +class EnumMember: def __init__(self, enum_name, name, value): self._name = name self._value = value @@ -14,3 +14,9 @@ def value(self): def __repr__(self): return f'{self._enum_name}{{{self._name}}}' + + def __eq__(self, other): + return self is other or self._value == other + + def __ne__(self, other): + return not self.__eq__(other) diff --git a/thingsdb/util/convert.py b/thingsdb/util/convert.py index 0b7f2c6..c3a9904 100644 --- a/thingsdb/util/convert.py +++ b/thingsdb/util/convert.py @@ -11,4 +11,4 @@ def convert(arg): if isinstance(arg, (list, tuple)): return [convert(v) for v in arg] - return arg + return getattr(arg, '_value', arg) diff --git a/thingsdb/util/fmt.py b/thingsdb/util/fmt.py index 7f333d2..f551319 100644 --- a/thingsdb/util/fmt.py +++ b/thingsdb/util/fmt.py @@ -9,8 +9,6 @@ def _wrap(value, blobs): name = f'blob{idx}' blobs[name] = value return name - if isinstance(value, Member): - return value.name() if isinstance(value, dict): thing_id = value.get('#') if thing_id is None: diff --git a/thingsdb/version.py b/thingsdb/version.py index e2f45ae..7c9e66e 100644 --- a/thingsdb/version.py +++ b/thingsdb/version.py @@ -1 +1 @@ -__version__ = '0.6.5' +__version__ = '0.6.6' From 866b22380dbeabb0590e4eab993002c1a5bf36e9 Mon Sep 17 00:00:00 2001 From: Jeroen van der Heijden Date: Fri, 12 Jun 2020 15:18:36 +0200 Subject: [PATCH 2/3] Added key lookup enum --- sample_lego.py | 19 ++++++++++++------- thingsdb/model/enum.py | 21 ++++++++++++++++++++- thingsdb/model/enummember.py | 11 +++++++---- 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/sample_lego.py b/sample_lego.py index 625a091..d8fe0c9 100644 --- a/sample_lego.py +++ b/sample_lego.py @@ -6,8 +6,8 @@ class Color(Enum): RED = "#f00" - BLUE = "#0f0" - GREEN = "#00f" + GREEN = "#0f0" + BLUE = "#00f" class Brick(Thing): @@ -53,17 +53,22 @@ async def example(): # ... now the collection will be watched for 100 seconds while True: await asyncio.sleep(3) - print('Color:', Color.GREEN is lego.bricks[0].color) - print('Is Enum', isinstance(Color.GREEN, Enum)) - print('Is EnumMember', isinstance(Color.GREEN, EnumMember)) - print('Is Color', isinstance(Color.GREEN, Color)) + if lego and lego.bricks: + + print('Color:', Color.RED is lego.bricks[0].color) + print('Is Enum', isinstance(Color.GREEN, Enum)) + print('Is EnumMember', isinstance(Color.GREEN, EnumMember)) + print('Is Color', isinstance(Color.GREEN, Color)) + print('Is True', Color.GREEN == Color("#0f0")) + print('Is True', Color.GREEN == Color["GREEN"]) + brick = lego.bricks[0] await brick.emit('new-color', 'RED') break await lego.query('.bricks.push(Brick{});') - await asyncio.sleep(30) + await asyncio.sleep(5) finally: client.close() diff --git a/thingsdb/model/enum.py b/thingsdb/model/enum.py index 005c3fe..c48bf98 100644 --- a/thingsdb/model/enum.py +++ b/thingsdb/model/enum.py @@ -2,10 +2,29 @@ _enums_lookup = {} # enums lookup by name -class Enum: + +class _GetAttr(type): + def __getitem__(cls, name): + for k, v in cls.__dict__.items(): + if k == name: + return v + raise KeyError(f'no member with name `{k}`') + + +class Enum(metaclass=_GetAttr): _visited = 0 # For build, 0=not visited, 1=new_type, 2=set_type, 3=build + def __new__(cls, *args): + if len(args) != 1: + return super().__new__(cls) + + value = args[0] + for v in cls.__dict__.values(): + if isinstance(v, EnumMember) and v._value == value: + return v + raise ValueError(f'no member with value `{value}`') + def __init_subclass__(cls, **kwargs): if issubclass(cls, EnumMember): return diff --git a/thingsdb/model/enummember.py b/thingsdb/model/enummember.py index 60eafa9..723e606 100644 --- a/thingsdb/model/enummember.py +++ b/thingsdb/model/enummember.py @@ -1,8 +1,4 @@ class EnumMember: - def __init__(self, enum_name, name, value): - self._name = name - self._value = value - self._enum_name = enum_name @property def name(self): @@ -12,6 +8,13 @@ def name(self): def value(self): return self._value + def __new__(cls, enum_name, name, value): + instance = object.__new__(cls) + instance._name = name + instance._value = value + instance._enum_name = enum_name + return instance + def __repr__(self): return f'{self._enum_name}{{{self._name}}}' From c97e7a95e99e6871fb324a5e0389d7b9cdacd728 Mon Sep 17 00:00:00 2001 From: Jeroen van der Heijden Date: Fri, 12 Jun 2020 15:23:37 +0200 Subject: [PATCH 3/3] Update sample --- sample_lego.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sample_lego.py b/sample_lego.py index d8fe0c9..afd60c6 100644 --- a/sample_lego.py +++ b/sample_lego.py @@ -62,6 +62,9 @@ async def example(): print('Is Color', isinstance(Color.GREEN, Color)) print('Is True', Color.GREEN == Color("#0f0")) print('Is True', Color.GREEN == Color["GREEN"]) + print('Is True', getattr(Color, 'GREEN') == Color["GREEN"]) + print('Is True', Color.RED.value == lego.bricks[0].color.value) + print('Is True', Color.RED.value == lego.bricks[0].color._value) brick = lego.bricks[0] await brick.emit('new-color', 'RED')