Skip to content

Commit

Permalink
Add ref, bump version, undo overzealous __slots__ usage for globals (#…
Browse files Browse the repository at this point in the history
…105)

* add ref function
* bump version to 0.13.0
* undo overzealous __slots__ usage for globals
  • Loading branch information
Korijn authored Nov 8, 2023
1 parent a07efd3 commit c295c76
Show file tree
Hide file tree
Showing 8 changed files with 57 additions and 41 deletions.
3 changes: 2 additions & 1 deletion observ/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
__version__ = "0.12.0"
__version__ = "0.13.0"


from .proxy import (
reactive,
readonly,
ref,
shallow_reactive,
shallow_readonly,
to_raw,
Expand Down
3 changes: 2 additions & 1 deletion observ/dict_proxy.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from .proxy import Proxy, TYPE_LOOKUP
from .proxy_db import proxy_db
from .traps import construct_methods_traps_dict, trap_map, trap_map_readonly


Expand Down Expand Up @@ -52,7 +53,7 @@

class DictProxyBase(Proxy):
def _orphaned_keydeps(self):
return set(self.proxy_db.attrs(self)["keydep"].keys()) - set(self.target.keys())
return set(proxy_db.attrs(self)["keydep"].keys()) - set(self.target.keys())


def readonly_dict_proxy_init(self, target, shallow=False, **kwargs):
Expand Down
32 changes: 14 additions & 18 deletions observ/proxy.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from functools import partial

from .dep import Dep
from .proxy_db import proxy_db


Expand All @@ -17,18 +16,16 @@ class Proxy:
"""

__hash__ = None
__slots__ = ["target", "readonly", "shallow", "proxy_db", "Dep", "__weakref__"]
__slots__ = ["target", "readonly", "shallow", "__weakref__"]

def __init__(self, target, readonly=False, shallow=False):
self.target = target
self.readonly = readonly
self.shallow = shallow
self.proxy_db = proxy_db
self.proxy_db.reference(self)
self.Dep = Dep
proxy_db.reference(self)

def __del__(self):
self.proxy_db.dereference(self)
proxy_db.dereference(self)


# Lookup dict for mapping a type (dict, list, set) to a method
Expand Down Expand Up @@ -63,22 +60,21 @@ def proxy(target, readonly=False, shallow=False):
if existing_proxy is not None:
return existing_proxy

# We can only wrap the following datatypes
if not isinstance(target, (dict, list, tuple, set)):
return target

# Otherwise, create a new proxy
proxy_type = None

# Create a new proxy
for target_type, (writable_proxy_type, readonly_proxy_type) in TYPE_LOOKUP.items():
if isinstance(target, target_type):
proxy_type = readonly_proxy_type if readonly else writable_proxy_type
break
else:
if isinstance(target, tuple):
return tuple(proxy(x, readonly=readonly, shallow=shallow) for x in target)
return proxy_type(target, readonly=readonly, shallow=shallow)

if isinstance(target, tuple):
return tuple(proxy(x, readonly=readonly, shallow=shallow) for x in target)

# We can't proxy a plain value
return target


return proxy_type(target, readonly=readonly, shallow=shallow)
def ref(target):
return proxy({"value": target})


reactive = proxy
Expand Down
21 changes: 11 additions & 10 deletions observ/traps.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from .dep import Dep
from .proxy import proxy
from .proxy_db import proxy_db


class ReadonlyError(Exception):
Expand All @@ -18,8 +19,8 @@ def read_trap(method, obj_cls):

@wraps(fn)
def trap(self, *args, **kwargs):
if self.Dep.stack:
self.proxy_db.attrs(self)["dep"].depend()
if Dep.stack:
proxy_db.attrs(self)["dep"].depend()
value = fn(self.target, *args, **kwargs)
if self.shallow:
return value
Expand All @@ -33,8 +34,8 @@ def iterate_trap(method, obj_cls):

@wraps(fn)
def trap(self, *args, **kwargs):
if self.Dep.stack:
self.proxy_db.attrs(self)["dep"].depend()
if Dep.stack:
proxy_db.attrs(self)["dep"].depend()
iterator = fn(self.target, *args, **kwargs)
if self.shallow:
return iterator
Expand All @@ -54,9 +55,9 @@ def read_key_trap(method, obj_cls):

@wraps(fn)
def trap(self, *args, **kwargs):
if self.Dep.stack:
if Dep.stack:
key = args[0]
keydeps = self.proxy_db.attrs(self)["keydep"]
keydeps = proxy_db.attrs(self)["keydep"]
if key not in keydeps:
keydeps[key] = Dep()
keydeps[key].depend()
Expand All @@ -75,7 +76,7 @@ def write_trap(method, obj_cls):
def trap(self, *args, **kwargs):
old = self.target.copy()
retval = fn(self.target, *args, **kwargs)
attrs = self.proxy_db.attrs(self)
attrs = proxy_db.attrs(self)
if obj_cls == dict:
change_detected = False
keydeps = attrs["keydep"]
Expand Down Expand Up @@ -104,7 +105,7 @@ def write_key_trap(method, obj_cls):
@wraps(fn)
def trap(self, *args, **kwargs):
key = args[0]
attrs = self.proxy_db.attrs(self)
attrs = proxy_db.attrs(self)
is_new = key not in attrs["keydep"]
old_value = getitem_fn(self.target, key) if not is_new else None
retval = fn(self.target, *args, **kwargs)
Expand All @@ -129,7 +130,7 @@ def delete_trap(method, obj_cls):
@wraps(fn)
def trap(self, *args, **kwargs):
retval = fn(self.target, *args, **kwargs)
attrs = self.proxy_db.attrs(self)
attrs = proxy_db.attrs(self)
attrs["dep"].notify()
for key in self._orphaned_keydeps():
attrs["keydep"][key].notify()
Expand All @@ -146,7 +147,7 @@ def delete_key_trap(method, obj_cls):
def trap(self, *args, **kwargs):
retval = fn(self.target, *args, **kwargs)
key = args[0]
attrs = self.proxy_db.attrs(self)
attrs = proxy_db.attrs(self)
attrs["dep"].notify()
attrs["keydep"][key].notify()
del attrs["keydep"][key]
Expand Down
12 changes: 5 additions & 7 deletions observ/watcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def decorator_computed(fn: T) -> T:
def getter():
if watcher.dirty:
watcher.evaluate()
if watcher.Dep.stack:
if Dep.stack:
watcher.depend()
return watcher.value

Expand Down Expand Up @@ -126,7 +126,6 @@ class Watcher:
"lazy",
"dirty",
"value",
"Dep",
"_number_of_callback_args",
"__weakref__",
]
Expand All @@ -145,7 +144,6 @@ def __init__(
deep: Deep watch the watched value
callback: Method to call when value has changed
"""
self.Dep = Dep
self.id = next(_ids)
if callable(fn):
if is_bound_method(fn):
Expand Down Expand Up @@ -177,7 +175,7 @@ def update(self) -> None:
self.dirty = True
return

if self.Dep.stack and self.Dep.stack[-1] is self and self.no_recurse:
if Dep.stack and Dep.stack[-1] is self and self.no_recurse:
return
if self.sync:
self.run()
Expand Down Expand Up @@ -254,13 +252,13 @@ def _run_callback(self, *args) -> None:
del frames

def get(self) -> Any:
self.Dep.stack.append(self)
Dep.stack.append(self)
try:
value = self.fn()
if self.deep:
traverse(value)
finally:
self.Dep.stack.pop()
Dep.stack.pop()
self.cleanup_deps()
return value

Expand All @@ -280,7 +278,7 @@ def cleanup_deps(self) -> None:
def depend(self) -> None:
"""This function is used by other watchers to depend on everything
this watcher depends on."""
if self.Dep.stack:
if Dep.stack:
for dep in self._deps:
dep.depend()

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "observ"
version = "0.12.0"
version = "0.13.0"
description = "Reactive state management for Python"
authors = ["Korijn van Golen <korijn@gmail.com>", "Berend Klein Haneveld <berendkleinhaneveld@gmail.com>"]
license = "MIT"
Expand Down
2 changes: 0 additions & 2 deletions tests/test_collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,6 @@
"target",
"readonly",
"shallow",
"proxy_db",
"Dep",
"__weakref__",
}

Expand Down
23 changes: 22 additions & 1 deletion tests/test_usage.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import pytest

from observ import computed, reactive, to_raw, watch
from observ import computed, reactive, ref, to_raw, watch
from observ.list_proxy import ListProxy
from observ.proxy import Proxy
from observ.watcher import WrongNumberOfArgumentsError
Expand Down Expand Up @@ -778,3 +778,24 @@ def test_watch_list_of_reactive_objects():
b.append("foo")

assert cb.call_count == 2


def test_usage_ref():
counter = ref(0)
called = 0
values = ()

def _callback(new, old):
nonlocal called
nonlocal values
called += 1
values = (new, old)

watcher = watch(lambda: counter["value"], _callback, sync=True)

assert not watcher.dirty
assert called == 0
counter["value"] += 1
assert called == 1
assert values[0] == 1
assert values[1] == 0

0 comments on commit c295c76

Please sign in to comment.