Add python proxy/filter models
This commit is contained in:
		
							
								
								
									
										102
									
								
								src/backend/models/filters.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								src/backend/models/filters.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,102 @@
 | 
			
		||||
# SPDX-License-Identifier: LGPL-3.0-or-later
 | 
			
		||||
 | 
			
		||||
from typing import TYPE_CHECKING, Collection, Dict, Optional, Tuple
 | 
			
		||||
 | 
			
		||||
from . import SyncId
 | 
			
		||||
from .model import Model
 | 
			
		||||
from .proxy import ModelProxy
 | 
			
		||||
 | 
			
		||||
if TYPE_CHECKING:
 | 
			
		||||
    from .model_item import ModelItem
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ModelFilter(ModelProxy):
 | 
			
		||||
    def __init__(self, sync_id: SyncId) -> None:
 | 
			
		||||
        self.filtered_out: Dict[Tuple[Optional[SyncId], str], "ModelItem"] = {}
 | 
			
		||||
        super().__init__(sync_id)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def accept_item(self, item: "ModelItem") -> bool:
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def source_item_set(self, source: Model, key, value: "ModelItem") -> None:
 | 
			
		||||
        if self.accept_source(source):
 | 
			
		||||
            dct = self if self.accept_item(value) else self.filtered_out
 | 
			
		||||
            dct[source.sync_id, key] = value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def source_item_deleted(self, source: Model, key) -> None:
 | 
			
		||||
        if self.accept_source(source):
 | 
			
		||||
            try:
 | 
			
		||||
                del self[source.sync_id, key]
 | 
			
		||||
            except KeyError:
 | 
			
		||||
                del self.filtered_out[source.sync_id, key]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def source_cleared(self, source: Model) -> None:
 | 
			
		||||
        if self.accept_source(source):
 | 
			
		||||
            for source_sync_id, key in self.copy():
 | 
			
		||||
                if source_sync_id == source.sync_id:
 | 
			
		||||
                    try:
 | 
			
		||||
                        del self[source.sync_id, key]
 | 
			
		||||
                    except KeyError:
 | 
			
		||||
                        del self.filtered_out[source.sync_id, key]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def refilter(self) -> None:
 | 
			
		||||
        take_out   = []
 | 
			
		||||
        bring_back = []
 | 
			
		||||
 | 
			
		||||
        for key, item in self.items():
 | 
			
		||||
            if not self.accept_item(item):
 | 
			
		||||
                take_out.append(key)
 | 
			
		||||
 | 
			
		||||
        for key, item in self.filtered_out.items():
 | 
			
		||||
            if self.accept_item(item):
 | 
			
		||||
                bring_back.append(key)
 | 
			
		||||
 | 
			
		||||
        for key in take_out:
 | 
			
		||||
            self.filtered_out[key] = self.pop(key)
 | 
			
		||||
 | 
			
		||||
        for key in bring_back:
 | 
			
		||||
            self[key] = self.filtered_out.pop(key)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FieldSubstringFilter(ModelFilter):
 | 
			
		||||
    def __init__(self, fields: Collection[str], *args, **kwargs) -> None:
 | 
			
		||||
        self.fields:  Collection[str] = fields
 | 
			
		||||
        self._filter: str             = ""
 | 
			
		||||
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def filter(self) -> str:
 | 
			
		||||
        return self._filter
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @filter.setter
 | 
			
		||||
    def filter(self, value: str) -> None:
 | 
			
		||||
        self._filter = value
 | 
			
		||||
        self.refilter()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def accept_item(self, item: "ModelItem") -> bool:
 | 
			
		||||
        if not self.filter:
 | 
			
		||||
            return True
 | 
			
		||||
 | 
			
		||||
        text       = " ".join((getattr(item, f) for f in self.fields))
 | 
			
		||||
        filt       = self.filter
 | 
			
		||||
        filt_lower = filt.lower()
 | 
			
		||||
 | 
			
		||||
        if filt_lower == filt:
 | 
			
		||||
            # Consider case only if filter isn't all lowercase (smart case)
 | 
			
		||||
            filt = filt_lower
 | 
			
		||||
            text = text.lower()
 | 
			
		||||
 | 
			
		||||
        for word in filt.split():
 | 
			
		||||
            if word and word not in text:
 | 
			
		||||
                return False
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
@@ -13,6 +13,7 @@ from . import SyncId
 | 
			
		||||
 | 
			
		||||
if TYPE_CHECKING:
 | 
			
		||||
    from .model_item import ModelItem
 | 
			
		||||
    from .proxy import ModelProxy  # noqa
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Model(MutableMapping):
 | 
			
		||||
@@ -29,6 +30,10 @@ class Model(MutableMapping):
 | 
			
		||||
    Items in the model are kept sorted using the `ModelItem` subclass `__lt__`.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    instances: Dict[SyncId, "Model"]      = {}
 | 
			
		||||
    proxies:   Dict[SyncId, "ModelProxy"] = {}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def __init__(self, sync_id: Optional[SyncId]) -> None:
 | 
			
		||||
        self.sync_id:      Optional[SyncId]       = sync_id
 | 
			
		||||
        self._data:        Dict[Any, "ModelItem"] = {}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										44
									
								
								src/backend/models/proxy.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/backend/models/proxy.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
# SPDX-License-Identifier: LGPL-3.0-or-later
 | 
			
		||||
 | 
			
		||||
from typing import TYPE_CHECKING
 | 
			
		||||
 | 
			
		||||
from . import SyncId
 | 
			
		||||
from .model import Model
 | 
			
		||||
 | 
			
		||||
if TYPE_CHECKING:
 | 
			
		||||
    from .model_item import ModelItem
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ModelProxy(Model):
 | 
			
		||||
    def __init__(self, sync_id: SyncId) -> None:
 | 
			
		||||
        super().__init__(sync_id)
 | 
			
		||||
        Model.proxies[sync_id] = self
 | 
			
		||||
 | 
			
		||||
        for sync_id, model in Model.instances.items():
 | 
			
		||||
            if sync_id != self.sync_id and self.accept_source(model):
 | 
			
		||||
                for key, item in model.items():
 | 
			
		||||
                    # if isinstance(model, ModelProxy):
 | 
			
		||||
                        # key = key[1]
 | 
			
		||||
 | 
			
		||||
                    self.source_item_set(model, key, item)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def accept_source(self, source: Model) -> bool:
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def source_item_set(self, source: Model, key, value: "ModelItem") -> None:
 | 
			
		||||
        if self.accept_source(source):
 | 
			
		||||
            self[source.sync_id, key] = value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def source_item_deleted(self, source: Model, key) -> None:
 | 
			
		||||
        if self.accept_source(source):
 | 
			
		||||
            del self[source.sync_id, key]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def source_cleared(self, source: Model) -> None:
 | 
			
		||||
        if self.accept_source(source):
 | 
			
		||||
            for source_sync_id, key in self.copy():
 | 
			
		||||
                if source_sync_id == source.sync_id:
 | 
			
		||||
                    del self[source_sync_id, key]
 | 
			
		||||
							
								
								
									
										17
									
								
								src/backend/models/special_models.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/backend/models/special_models.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
# SPDX-License-Identifier: LGPL-3.0-or-later
 | 
			
		||||
 | 
			
		||||
from .filters import FieldSubstringFilter
 | 
			
		||||
from .model import Model
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AllRooms(FieldSubstringFilter):
 | 
			
		||||
    def __init__(self) -> None:
 | 
			
		||||
        super().__init__(sync_id="all_rooms", fields=("display_name",))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def accept_source(self, source: Model) -> bool:
 | 
			
		||||
        return (
 | 
			
		||||
            isinstance(source.sync_id, tuple) and
 | 
			
		||||
            len(source.sync_id) == 2 and
 | 
			
		||||
            source.sync_id[1] == "rooms"  # type: ignore
 | 
			
		||||
        )
 | 
			
		||||
		Reference in New Issue
	
	Block a user