async def test_pri(client, loop): """Testing prioritized reading""" logger.debug("START subdir") d = dict types = EtcTypes() t = client w = await t.tree(("two", ), immediate=False, static=False) d1 = d(pri=d(a="1", b="2", c="3", d="4", e="5")) await w.update(d1) await w.close() class CheckFirst(EtcString): async def init(self): assert ilen(self.parent.values()) == 1, self.parent._data class CheckLast(EtcString): async def init(self): assert ilen(self.parent.values()) == 5, self.parent._data types.register("pri", "c", cls=CheckFirst, pri=1) types.register("pri", "d", cls=CheckLast, pri=-1) types.register("pri", "b", cls=CheckLast, pri=-1) w = await t.tree(("two", ), immediate=None, static=False, types=types) wd = w['pri'] assert isinstance(wd, EtcAwaiter) wd = await wd assert isinstance(wd, EtcDir), wd assert wd['a'] == "1" assert wd['b'] == "2" assert wd['c'] == "3" await w.close()
async def do_typed(client, loop, subtyped, recursed): # object type registration types = EtcTypes() if subtyped: class Sub(EtcDir): def __init__(self, *a, pre=None, **k): super().__init__(*a, **k, pre=pre) assert pre['my_value'].value == '10' self._types = EtcTypes() self._types.register('my_value', cls=EtcInteger) def subtype(self, *path, pre=None, recursive=None, **kw): if path == ('my_value', ): if pre is None: raise ReloadData assert pre.value == "10", pre elif path == ('a', 'b', 'c'): if pre is None: raise ReloadData # yes, I'm bad elif not recursive: raise ReloadRecursive elif not recursive: assert len(path) < 3 return super().subtype(*path, pre=pre, recursive=recursive, **kw) types.register('here', cls=Sub) else: types.register('here', 'my_value', cls=EtcInteger) d = dict t = client d1 = d(types=d(here=d(my_value='10', a=d(b=d(c=d(d=d(e='20'))))))) await t._f(d1) w = await t.tree("/types", immediate=recursed, static=True, types=types) v = await w['here'].get('my_value', raw=True) assert v.value == 10, w['here'].get('my_value', raw=True) v = await w['here'] assert v['my_value'] == 10, v.get('my_value', raw=True) assert (recursed is None) == (type(v['a']['b']['c']) is EtcAwaiter) await v['a']['b']['c'] assert not type(v['a']['b']['c']) is EtcAwaiter assert (type(v['a']['b']['c']['d']) is EtcAwaiter) == (recursed is None and not subtyped) await v['a']['b']['c']['d'] # no-op assert v['a']['b']['c']['d']['e'] == '20' assert isinstance(v, Sub if subtyped else EtcDir) await w.close()
class Sub(EtcDir): def __init__(self,*a,pre=None,**k): super().__init__(*a,**k,pre=pre) assert pre['my_value'].value == '10' self._types = EtcTypes() self._types.register('my_value',cls=EtcInteger) def subtype(self,*path,pre=None,recursive=None,**kw): if path == ('my_value',): if pre is None: raise ReloadData assert pre.value=="10",pre elif path == ('a','b','c'): if pre is None: raise ReloadData # yes, I'm bad elif not recursive: raise ReloadRecursive elif not recursive: assert len(path)<3 return super().subtype(*path,pre=pre,recursive=recursive,**kw)
async def do_typed(client,loop, subtyped,recursed): # object type registration types = EtcTypes() if subtyped: class Sub(EtcDir): def __init__(self,*a,pre=None,**k): super().__init__(*a,**k,pre=pre) assert pre['my_value'].value == '10' self._types = EtcTypes() self._types.register('my_value',cls=EtcInteger) def subtype(self,*path,pre=None,recursive=None,**kw): if path == ('my_value',): if pre is None: raise ReloadData assert pre.value=="10",pre elif path == ('a','b','c'): if pre is None: raise ReloadData # yes, I'm bad elif not recursive: raise ReloadRecursive elif not recursive: assert len(path)<3 return super().subtype(*path,pre=pre,recursive=recursive,**kw) types.register('here',cls=Sub) else: types.register('here','my_value',cls=EtcInteger) d=dict t = client d1=d(types=d(here=d(my_value='10',a=d(b=d(c=d(d=d(e='20'))))))) await t._f(d1) w = await t.tree("/types", immediate=recursed, static=True, types=types) v = await w['here']._get('my_value') assert v.value == 10,w['here']._get('my_value') v = await w['here'] assert v['my_value'] == 10, v._get('my_value') assert (recursed is None) == (type(v['a']['b']['c']) is EtcAwaiter) await v['a']['b']['c'] assert not type(v['a']['b']['c']) is EtcAwaiter assert (type(v['a']['b']['c']['d']) is EtcAwaiter) == (recursed is None and not subtyped) await v['a']['b']['c']['d'] # no-op assert v['a']['b']['c']['d']['e'] == '20' assert isinstance(v, Sub if subtyped else EtcDir)
class Sub(EtcDir): def __init__(self, *a, pre=None, **k): super().__init__(*a, **k, pre=pre) assert pre['my_value'].value == '10' self._types = EtcTypes() self._types.register('my_value', cls=EtcInteger) def subtype(self, *path, pre=None, recursive=None, **kw): if path == ('my_value', ): if pre is None: raise ReloadData assert pre.value == "10", pre elif path == ('a', 'b', 'c'): if pre is None: raise ReloadData # yes, I'm bad elif not recursive: raise ReloadRecursive elif not recursive: assert len(path) < 3 return super().subtype(*path, pre=pre, recursive=recursive, **kw)
async def test_basic_watch(client, loop): """Watches which don't actually watch""" # object type registration types = EtcTypes() twotypes = EtcTypes() @twotypes.register() class rTwo(EtcDir): pass class rDie(EtcValue): async def has_update(self): raise RuntimeError("RIP") @twotypes.register("die") class rPreDie(EtcValue): @classmethod async def this_obj(cls, recursive=None, **kw): return rDie(**kw) # reg funcion shall return the right thing types.step('two', dest=twotypes) assert types[('two', 'die')] is rPreDie assert types.lookup(('two', 'die'), dir=False) is rPreDie assert types.lookup('two', 'die', dir=False) is rPreDie assert types.lookup('two/die', dir=False) is rPreDie assert types.lookup('two', dir=True, raw=True).lookup('die', dir=False) is rPreDie assert types.lookup('two/die', dir=False, raw=True).lookup(dir=False) is rPreDie i = types.register("two", "vier", cls=EtcBoolean) assert i is EtcBoolean i = types.register("*/vierixx")(EtcInteger) assert i is EtcInteger types['what/ever'] = EtcFloat types['what/ever'] = rTwo assert types.lookup('what', 'ever', dir=False) is EtcFloat assert types.lookup('what', 'ever', dir=True) is rTwo assert types['what/ever'] is EtcFloat with pytest.raises(AssertionError): types['/what/ever'] with pytest.raises(AssertionError): types['what/ever/'] with pytest.raises(AssertionError): types['what//ever'] types['something/else'] = EtcInteger assert types['two/vier'] is EtcBoolean assert types['something/else'] is EtcInteger assert types['not/not'] is None d = dict t = client d1 = d(one="eins", two=d(zwei=d(und="drei", a=d(b=d(c='d'))), vier="true"), x="y") await t._f(d1) # basic access, each directory separately class xRoot(EtcRoot): pass types.register(cls=xRoot) @xRoot.register("zwei", "und") class xUnd(EtcString): pass w = await t.tree("/two", immediate=False, static=True, types=types) w.env.foobar = "Foo Bar" assert sorted(dict( (a, b) for a, b, c in w.registrations()).items()) == sorted([ (('.', ), [None, xRoot]), (('.', 'something', 'else'), [EtcInteger, None]), (('.', '*', 'vierixx'), [EtcInteger, None]), (('.', 'what', 'ever'), [EtcFloat, rTwo]), (('.', 'two'), [None, rTwo]), (('.', 'two', 'die'), [rPreDie, None]), (('.', 'two', 'vier'), [EtcBoolean, None]), (('zwei', 'und'), [xUnd, None]), ]), list(w.registrations()) assert isinstance(w, xRoot) assert w.env.foobar == "Foo Bar" assert w.env.barbaz is None assert w['zwei'].env is w.env assert w['zwei']['a']['b'].env is w.env assert w['zwei']['und'] == "drei" assert type(w['zwei'].get('und', raw=True)) is xUnd assert w['vier'] == "true" with pytest.raises(KeyError): w['x'] # basic access, read it all at once w2 = await t.tree("/two", immediate=True, static=True, types=types) assert w2['zwei']['und'] == "drei" assert w['vier'] == "true" assert w == w2 # basic access, read it on demand w5 = await t.tree("/two", immediate=None, types=types) def wx(x): assert x.added == {'und', 'a'} x.test_called = 1 mx = w5['zwei'].add_monitor(wx) assert isinstance(w5['zwei']['und'], EtcAwaiter) assert (await w5['zwei']['und']).value == "drei" assert w5['vier'] == "true" await w5['zwei'].force_updated() assert w5['zwei'].test_called # use typed subtrees w4 = await t.tree((), types=types) await w4.set('two', d(sechs="sieben")) w3 = await t.tree("/", static=True, types=types) assert w3['two']['vier'] is True assert w3['two']['sechs'] == "sieben" ##assert not w3['two'] == w2 # which are different, but not because of the tree types assert not w3 is w4 assert w3 == w4 # check basic node iterators res = set() for v in w3['two']['zwei'].values(): assert not isinstance(v, EtcValue) if not isinstance(v, EtcDir): res.add(v) assert res == {"drei"} res = set() for k in w3['two'].keys(): res.add(k) assert res == {"zwei", "vier", "sechs"} res = set() for k, v in w3['two'].items(): res.add(k) assert v == w3['two'][k] assert res == {"zwei", "vier", "sechs"} # check what happens if an updater dies on us await w4['two'].set('hello', 'one') await w4['two'].set('die', '42') await asyncio.sleep(1.5, loop=loop) with pytest.raises(RuntimeError): await w4['two'].set('hello', 'two') await w.close() await w2.close() await w3.close() await w4.close() await w5.close()
async def test_update_watch(client, loop): """Testing auto-update, both ways""" logger.debug("START update_watch") d = dict types = EtcTypes() t = client w = await t.tree(("two", ), immediate=False, static=False) d1 = d(zwei=d(und="drei", oder={}), vier="fünf", sechs="sieben", acht=d(neun="zehn")) await w.update(d1) m1, m2 = Mock(), Mock() f = asyncio.Future(loop=loop) def wake(x): f.set_result(x) def mx(x): s = getattr(x, 'test_step', 0) x.test_step = s + 1 if s == 0: assert x.added == {'und', 'oder'} assert x.deleted == {'oder'} elif s == 1: assert x.added == {':zehn'} assert not x.deleted else: assert 0, s pass i0 = w.add_monitor(wake) i1 = w['zwei'].add_monitor(m1) ix = w['zwei'].add_monitor(mx) i2 = w['zwei'].get('und', raw=True).add_monitor(m2) assert w['sechs'] == "sieben" acht = w['acht'] assert acht['neun'] == "zehn" d2 = d(two=d(zwei=d(und="mehr"), vier=d(auch="xxy", oder="fünfe"))) mod = await t._f(d2, delete=True) await w.wait(mod=mod, tasks=True) assert w['zwei']['und'] == "mehr" assert w['vier']['oder'] == "fünfe" assert w['vier']['auch'] == "xxy" assert "oder" in w['vier'] assert "oderr" not in w['vier'] # Directly insert "deep" entries await t.client.write(client._extkey('/two/three/four/five/six/seven'), value=None, dir=True) mod = (await t.client.write(client._extkey('/two/three/four/fiver'), "what")).modifiedIndex await w.wait(mod, tasks=True) # and check that they're here assert w['three']['four']['fiver'] == "what" assert isinstance(w['three']['four']['five']['six']['seven'], EtcDir) logger.debug("Waiting for _update 1") await f assert w['zwei'].test_step == 1 f = asyncio.Future(loop=loop) assert m1.call_count # may be >1 assert m2.call_count mc1 = m1.call_count mc2 = m2.call_count w['zwei'].remove_monitor(i1) # The ones deleted by _f(…,delete=True) should not be with pytest.raises(KeyError): w['sechs'] with pytest.raises(KeyError): logger.debug("CHECK acht") w['acht'] # deleting a whole subtree is not yet implemented with pytest.raises((etcd.EtcdDirNotEmpty, etcd.EtcdNotFile)): del w['vier'] await w.wait(tasks=True) del w['vier']['oder'] await w.wait(tasks=True) w['vier'] s = w['vier'].get('auch', raw=True)._cseq with pytest.raises(KeyError): w['vier']['oder'] m = await w['vier'].get('auch', raw=True).delete() await w.wait(m, tasks=True) with pytest.raises(KeyError): w['vier']['auch'] # Now test that adding a node does the right thing m = await w['vier'].set('auch', "ja2") logger.debug("Set :zehn") w['zwei'][':zehn'] = d(zwanzig=30, vierzig=d(fuenfzig=60)) w['zwei']['und'] = "weniger" logger.debug("WAIT FOR ME") await w['zwei'].wait(m, tasks=True) assert s != w['vier'].get('auch', raw=True)._cseq from etcd_tree import client as rclient from .util import cfgpath tt = await rclient(cfgpath, loop=loop) w1 = await tt.tree("/two", immediate=True, types=types) assert w is not w1 assert w == w1 # wx = await tt.tree("/two", immediate=True) # assert wx is w1 ## no caching w2 = await t.tree("/two", static=True) assert w1 is not w2 assert w1['zwei']['und'] == "weniger" assert w1['zwei'].get('und') == "weniger" assert w1['zwei'].get('und', raw=True).value == "weniger" assert w1['zwei'].get('und', 'nix') == "weniger" assert w1['zwei'].get('und', 'nix', raw=True).value == "weniger" assert w1['zwei'].get('huhuhu', 'nixi') == "nixi" assert w1['zwei'].get('huhuhu', 'nixo', raw=True) == "nixo" with pytest.raises(KeyError): w1['zwei'].get('huhuhu') with pytest.raises(KeyError): w1['zwei'].get('huhuhu', raw=True) assert w2['zwei']['und'] == "weniger" assert w1['zwei'][':zehn']['zwanzig'] == "30" assert w2['zwei'][':zehn']['zwanzig'] == "30" assert w1['vier']['auch'] == "ja2" assert w2['vier']['auch'] == "ja2" w1['zwei'] = d(und='noch weniger') await w1.wait(tasks=True) assert w1['zwei']['und'] == "noch weniger" assert w1['zwei'].get('und') == "noch weniger" logger.debug("Waiting for _update 2") await f assert w['zwei'].test_step == 2 assert m1.call_count == mc1 assert m2.call_count == mc2 + 1 # three ways to skin a cat del i0 # w['zwei'].remove_monitor(i1) ## happened above w['zwei'].remove_monitor(ix) i2.cancel() assert not w._later_mon assert not w['zwei']._later_mon assert not w['zwei'].get('und', raw=True)._later_mon types.register("**", "new_a", cls=IntObj) types.register(("**", "new_b"), cls=EtcInteger) mod = await t._f(d2, delete=True) await w1.wait(mod, tasks=True) w1['vier']['auch'] = "nein" #assert w1.vier.auch == "ja" ## should be, but too dependent on timing w1['vier']['new_a'] = 4242 await w1.wait(tasks=True) assert w1['vier']['auch'] == "nein" with pytest.raises(KeyError): assert w1['vier']['dud'] assert w1['vier']['new_a'].value == 4242 with pytest.raises(ValueError): await w1['vier'].set('new_a', "x123", ext=True) await w1['vier'].set('new_a', "123", ext=True) assert w1['vier']['new_a'].value == 123 d1 = d(two=d(vier=d(a="b", c="d"))) mod = await t._f(d1) await w1.wait(mod, tasks=True) assert w1['vier']['a'] == "b" with pytest.raises(KeyError): w1['vier']['new_b'] d1 = d(two=d(vier=d(c="x", d="y", new_b=123))) mod = await t._f(d1) await w1.wait(mod, tasks=True) assert w1['vier']['c'] == "x" assert w1['vier']['d'] == "y" assert w1['vier']['new_b'] == 123 await w.wait(mod, tasks=True) assert len(w['vier']) == 7, list(w['vier']) s = set(w['vier']) assert 'a' in s assert 'auch' in s assert 'auck' not in s # now delete the thing await w['vier'].delete('a') await w['vier'].delete('auch') await w['vier'].delete('oder') await w['vier'].delete('c') await w['vier'].delete('d') await w['vier'].delete('new_a') await w['vier'].delete('new_b') m = await w.delete('vier', recursive=False) await w.wait(m, tasks=True) with pytest.raises(KeyError): w['vier'] with pytest.raises(RuntimeError): await w.delete() assert w.running assert not w.stopped.done() await t.delete("/two", recursive=True) await asyncio.sleep(0.3, loop=loop) assert not w.running assert w.stopped.done() await w.close() await w1.close() await w2.close()
async def test_basic_watch(client,loop): """Watches which don't actually watch""" # object type registration types = EtcTypes() twotypes = EtcTypes() @twotypes.register() class rTwo(EtcDir): pass class rDie(EtcValue): def has_update(self): raise RuntimeError("RIP") @twotypes.register("die") class rPreDie(EtcValue): @classmethod async def this_obj(cls,recursive=None,**kw): return rDie(**kw) # reg funcion shall return the right thing types.step('two',dest=twotypes) assert types[('two','die')] is rPreDie assert types.lookup(('two','die'),dir=False) is rPreDie assert types.lookup('two','die',dir=False) is rPreDie assert types.lookup('two/die',dir=False) is rPreDie assert types.lookup('two',dir=True,raw=True).lookup('die',dir=False) is rPreDie assert types.lookup('two/die',dir=False,raw=True).lookup(dir=False) is rPreDie i = types.register("two","vier", cls=EtcInteger) assert i is EtcInteger i = types.register("*/vierixx")(EtcInteger) assert i is EtcInteger types['what/ever'] = EtcFloat assert types.lookup('what','ever', dir=False) is EtcFloat assert types['what/ever'] is EtcFloat with pytest.raises(AssertionError): types['/what/ever'] with pytest.raises(AssertionError): types['what/ever/'] with pytest.raises(AssertionError): types['what//ever'] types['something/else'] = EtcInteger assert types['two/vier'] is EtcInteger assert types['something/else'] is EtcInteger assert types['not/not'] is None d=dict t = client d1=d(one="eins",two=d(zwei=d(und="drei",a=d(b=d(c='d'))),vier="5"),x="y") await t._f(d1) # basic access, each directory separately class xRoot(EtcRoot): pass types.register(cls=xRoot) @xRoot.register("zwei","und") class xUnd(EtcString): pass w = await t.tree("/two", immediate=False, static=True, types=types) w.env.foobar="Foo Bar" assert isinstance(w,xRoot) assert w.env.foobar == "Foo Bar" assert w.env.barbaz is None assert w['zwei'].env is w.env assert w['zwei']['a']['b'].env is w.env assert w['zwei']['und'] == "drei" assert type(w['zwei']._get('und')) is xUnd assert w['vier'] == "5" with pytest.raises(KeyError): w['x'] # basic access, read it all at once w2 = await t.tree("/two", immediate=True, static=True, types=types) assert w2['zwei']['und'] == "drei" assert w['vier'] == "5" assert w == w2 # basic access, read it on demand w5 = await t.tree("/two", immediate=None, types=types) def wx(x): assert x.added == {'und','a'} x.test_called = 1 mx = w5['zwei'].add_monitor(wx) assert isinstance(w5['zwei']['und'],EtcAwaiter) assert (await w5['zwei']['und']).value == "drei" assert w5['vier'] == "5" w5['zwei'].force_updated() assert w5['zwei'].test_called # use typed subtrees w4 = await t.tree((), types=types) await w4.set('two',d(sechs="sieben")) w3 = await t.tree("/", static=True, types=types) assert w3['two']['vier'] == 5 assert w3['two']['sechs'] == "sieben" assert not w3['two'] == w2 # which are different, but not because of the tree types assert not w3 is w4 assert w3 == w4 # check basic node iterators res=set() for v in w3['two']['zwei'].values(): assert not isinstance(v,EtcValue) if not isinstance(v,EtcDir): res.add(v) assert res == {"drei"} res=set() for k in w3['two'].keys(): res.add(k) assert res == {"zwei","vier","sechs"} res=set() for k,v in w3['two'].items(): res.add(k) assert v == w3['two'][k] assert res == {"zwei","vier","sechs"} # check what happens if an updater dies on us await w4['two'].set('hello','one') await w4['two'].set('die',42) await asyncio.sleep(1.5, loop=loop) with pytest.raises(WatchStopped): await w4['two'].set('hello','two') await w2.close() await w3.close() await w4.close()
async def test_update_watch(client, loop): """Testing auto-update, both ways""" logger.debug("START update_watch") d=dict types = EtcTypes() t = client w = await t.tree(("two",), immediate=False, static=False) d1=d(zwei=d(und="drei",oder={}),vier="fünf",sechs="sieben",acht=d(neun="zehn")) await w.update(d1) m1,m2 = Mock(),Mock() f = asyncio.Future(loop=loop) def wake(x): f.set_result(x) def mx(x): s = getattr(x,'test_step',0) x.test_step = s+1 if s == 0: assert x.added == {'und','oder'} assert x.deleted == {'oder'} elif s == 1: assert x.added == {'zehn'} assert not x.deleted else: assert 0,s pass i0 = w.add_monitor(wake) i1 = w['zwei'].add_monitor(m1) ix = w['zwei'].add_monitor(mx) i2 = w['zwei']._get('und').add_monitor(m2) assert w['sechs'] == "sieben" acht = w['acht'] assert acht['neun'] =="zehn" d2=d(two=d(zwei=d(und="mehr"),vier=d(auch="xxy",oder="fünfe"))) mod = await t._f(d2,delete=True) await w.wait(mod=mod) assert w['zwei']['und']=="mehr" assert w['vier']['oder']=="fünfe" assert w['vier']['auch']=="xxy" assert "oder" in w['vier'] assert "oderr" not in w['vier'] # Directly insert "deep" entries await t.client.write(client._extkey('/two/three/four/five/six/seven'),value=None,dir=True) mod = (await t.client.write(client._extkey('/two/three/four/fiver'),"what")).modifiedIndex await w.wait(mod) # and check that they're here assert w['three']['four']['fiver'] == "what" assert isinstance(w['three']['four']['five']['six']['seven'], EtcDir) logger.debug("Waiting for _update 1") await f f = asyncio.Future(loop=loop) assert m1.call_count # may be >1 assert m2.call_count mc1 = m1.call_count mc2 = m2.call_count w['zwei'].remove_monitor(i1) # The ones deleted by _f(…,delete=True) should not be with pytest.raises(KeyError): w['sechs'] with pytest.raises(KeyError): logger.debug("CHECK acht") w['acht'] # deleting a whole subtree is not yet implemented with pytest.raises((etcd.EtcdDirNotEmpty,etcd.EtcdNotFile)): del w['vier'] await w.wait() del w['vier']['oder'] await w.wait() w['vier'] s = w['vier']._get('auch')._cseq with pytest.raises(KeyError): w['vier']['oder'] m = await w['vier']._get('auch').delete() await w.wait(m) with pytest.raises(KeyError): w['vier']['auch'] # Now test that adding a node does the right thing m = await w['vier'].set('auch',"ja2") w['zwei']['zehn'] = d(zwanzig=30,vierzig=d(fuenfzig=60)) w['zwei']['und'] = "weniger" logger.debug("WAIT FOR ME") await w['zwei'].wait(m) assert s != w['vier']._get('auch')._cseq from etcd_tree import client as rclient from .util import cfgpath tt = await rclient(cfgpath, loop=loop) w1 = await tt.tree("/two", immediate=True, types=types) assert w is not w1 assert w == w1 # wx = await tt.tree("/two", immediate=True) # assert wx is w1 ## no caching w2 = await t.tree("/two", static=True) assert w1 is not w2 assert w1['zwei']['und'] == "weniger" assert w1['zwei'].get('und') == "weniger" assert w1['zwei']._get('und').value == "weniger" assert w1['zwei'].get('und','nix') == "weniger" assert w1['zwei']._get('und','nix').value == "weniger" assert w1['zwei'].get('huhuhu','nixi') == "nixi" assert w1['zwei']._get('huhuhu','nixo') == "nixo" with pytest.raises(KeyError): w1['zwei'].get('huhuhu') with pytest.raises(KeyError): w1['zwei']._get('huhuhu') assert w2['zwei']['und'] == "weniger" assert w1['zwei']['zehn']['zwanzig'] == "30" assert w2['zwei']['zehn']['zwanzig'] == "30" assert w1['vier']['auch'] == "ja2" assert w2['vier']['auch'] == "ja2" w1['zwei']=d(und='noch weniger') await w1.wait() assert w1['zwei']['und'] == "noch weniger" assert w1['zwei'].get('und') == "noch weniger" logger.debug("Waiting for _update 2") await f assert m1.call_count == mc1 assert m2.call_count == mc2+1 # three ways to skin a cat del i0 # w['zwei'].remove_monitor(i1) ## happened above w['zwei'].remove_monitor(ix) i2.cancel() assert not w._later_mon assert not w['zwei']._later_mon assert not w['zwei']._get('und')._later_mon types.register("**","new_a", cls=IntObj) types.register(("**","new_b"), cls=EtcInteger) mod = await t._f(d2,delete=True) await w1.wait(mod) w1['vier']['auch'] = "nein" #assert w1.vier.auch == "ja" ## should be, but too dependent on timing w1['vier']['new_a'] = 4242 await w1.wait() assert w1['vier']['auch'] == "nein" with pytest.raises(KeyError): assert w1['vier']['dud'] assert w1['vier']['new_a'].value == 4242 d1=d(two=d(vier=d(a="b",c="d"))) mod = await t._f(d1) await w1.wait(mod) assert w1['vier']['a'] == "b" with pytest.raises(KeyError): w1['vier']['new_b'] d1=d(two=d(vier=d(c="x",d="y",new_b=123))) mod = await t._f(d1) await w1.wait(mod) assert w1['vier']['c'] == "x" assert w1['vier']['d'] == "y" assert w1['vier']['new_b'] == 123 await w.wait(mod) assert len(w['vier']) == 7,list(w['vier']) s=set(w['vier']) assert 'a' in s assert 'auch' in s assert 'auck' not in s # now delete the thing await w['vier'].delete('a') await w['vier'].delete('auch') await w['vier'].delete('oder') await w['vier'].delete('c') await w['vier'].delete('d') await w['vier'].delete('new_a') await w['vier'].delete('new_b') m = await w.delete('vier',recursive=False) await w.wait(m) with pytest.raises(KeyError): w['vier'] with pytest.raises(RuntimeError): await w.delete() assert w.running assert not w.stopped.done() await t.delete("/two",recursive=True) await asyncio.sleep(0.3,loop=loop) assert not w.running assert w.stopped.done() await w.close() await w1.close() await w2.close()