mirror of https://github.com/seebye/ueberzug
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
268 lines
7.9 KiB
Python
268 lines
7.9 KiB
Python
"""This module defines util classes
|
|
which allow to execute operations
|
|
for each element of a list of objects of the same class.
|
|
"""
|
|
import abc
|
|
import collections.abc
|
|
import functools
|
|
|
|
|
|
class SubclassingMeta(abc.ABCMeta):
|
|
"""Metaclass which creates a subclass for each instance.
|
|
|
|
As decorators only work
|
|
if the class object contains the declarations,
|
|
we need to create a subclass for each different type
|
|
if we want to dynamically use them.
|
|
"""
|
|
SUBCLASS_IDENTIFIER = '__subclassed__'
|
|
|
|
def __call__(cls, *args, **kwargs):
|
|
if hasattr(cls, SubclassingMeta.SUBCLASS_IDENTIFIER):
|
|
return super().__call__(*args, **kwargs)
|
|
|
|
subclass = type(cls.__name__, (cls,), {
|
|
SubclassingMeta.SUBCLASS_IDENTIFIER:
|
|
SubclassingMeta.SUBCLASS_IDENTIFIER})
|
|
return subclass(*args, **kwargs)
|
|
|
|
|
|
class BatchList(collections.abc.MutableSequence, metaclass=SubclassingMeta):
|
|
"""BatchList provides the execution of methods and field access
|
|
for each element of a list of instances of the same class
|
|
in a similar way to one of these instances it would.
|
|
"""
|
|
__attributes_declared = False
|
|
|
|
class BatchMember:
|
|
def __init__(self, outer, name):
|
|
"""
|
|
Args:
|
|
outer (BatchList): Outer class instance
|
|
"""
|
|
self.outer = outer
|
|
self.name = name
|
|
|
|
class BatchField(BatchMember):
|
|
def __get__(self, owner_instance, owner_class):
|
|
return BatchList([instance.__getattribute__(self.name)
|
|
for instance in self.outer])
|
|
|
|
def __set__(self, owner_instance, value):
|
|
for instance in self.outer:
|
|
instance.__setattr__(self.name, value)
|
|
|
|
def __delete__(self, instance):
|
|
for instance in self.outer:
|
|
instance.__delattr__(self.name)
|
|
|
|
class BatchMethod(BatchMember):
|
|
def __call__(self, *args, **kwargs):
|
|
return BatchList(
|
|
[instance.__getattribute__(self.name)(*args, **kwargs)
|
|
for instance in self.outer])
|
|
|
|
def __init__(self, collection: list):
|
|
"""
|
|
Args:
|
|
collection (List): List of target instances
|
|
"""
|
|
self.__collection = collection.copy()
|
|
self.__initialized = False
|
|
self.__type = None
|
|
self.entered = False
|
|
self.__attributes_declared = True
|
|
self.__init_members__()
|
|
|
|
def __call__(self, *args, **kwargs):
|
|
if self.__initialized:
|
|
raise TypeError("'%s' object is not callable" % self.__type)
|
|
return BatchList([])
|
|
|
|
def __getattr__(self, name):
|
|
if self.__initialized:
|
|
return AttributeError("'%s' object has no attribute '%s'"
|
|
% (self.__type, name))
|
|
return BatchList([])
|
|
|
|
def __setattr__(self, name, value):
|
|
if (not self.__attributes_declared or
|
|
self.__initialized or
|
|
not isinstance(getattr(self, name), BatchList)):
|
|
super().__setattr__(name, value)
|
|
|
|
def __init_members__(self):
|
|
if self.__collection and not self.__initialized:
|
|
# Note: We can't simply use the class,
|
|
# as the attributes exists only after the instantiation
|
|
self.__initialized = True
|
|
instance = self.__collection[0]
|
|
self.__type = type(instance)
|
|
self.__init_attributes__(instance)
|
|
self.__init_methods__(instance)
|
|
|
|
def __declare_decorator__(self, name, decorator):
|
|
setattr(type(self), name, decorator)
|
|
|
|
def __init_attributes__(self, target_instance):
|
|
for name in self.__get_public_attributes(target_instance):
|
|
self.__declare_decorator__(name, BatchList.BatchField(self, name))
|
|
|
|
@staticmethod
|
|
def __get_public_attributes(target_instance):
|
|
attributes = (vars(target_instance)
|
|
if hasattr(target_instance, '__dict__')
|
|
else [])
|
|
return (name for name in attributes
|
|
if not name.startswith('_'))
|
|
|
|
@staticmethod
|
|
@functools.lru_cache()
|
|
def __get_public_members(target_type):
|
|
members = {
|
|
name: member
|
|
for type_members in
|
|
map(vars, reversed(target_type.mro()))
|
|
for name, member in type_members.items()
|
|
}
|
|
return {
|
|
name: member
|
|
for name, member in members.items()
|
|
if not name.startswith('_')
|
|
}
|
|
|
|
def __init_methods__(self, target_instance):
|
|
public_members = self.__get_public_members(type(target_instance))
|
|
for name, value in public_members.items():
|
|
if callable(value):
|
|
self.__declare_decorator__(
|
|
name, BatchList.BatchMethod(self, name))
|
|
else:
|
|
# should be an decorator
|
|
self.__declare_decorator__(
|
|
name, BatchList.BatchField(self, name))
|
|
|
|
def __enter__(self):
|
|
self.entered = True
|
|
return BatchList([instance.__enter__() for instance in self])
|
|
|
|
def __exit__(self, *args):
|
|
for instance in self:
|
|
instance.__exit__(*args)
|
|
|
|
def __iadd__(self, other):
|
|
if self.entered:
|
|
for i in other:
|
|
i.__enter__()
|
|
self.__collection.__iadd__(other)
|
|
self.__init_members__()
|
|
return self
|
|
|
|
def append(self, item):
|
|
if self.entered:
|
|
item.__enter__()
|
|
self.__collection.append(item)
|
|
self.__init_members__()
|
|
|
|
def insert(self, index, item):
|
|
if self.entered:
|
|
item.__enter__()
|
|
self.__collection.insert(index, item)
|
|
self.__init_members__()
|
|
|
|
def extend(self, iterable):
|
|
for item in iterable:
|
|
self.append(item)
|
|
|
|
def __add__(self, other):
|
|
return BatchList(self.__collection.__add__(other))
|
|
|
|
def reverse(self):
|
|
self.__collection.reverse()
|
|
|
|
def clear(self):
|
|
if self.entered:
|
|
for i in self.__collection:
|
|
i.__exit__(None, None, None)
|
|
self.__collection.clear()
|
|
|
|
def copy(self):
|
|
return BatchList(self.__collection.copy())
|
|
|
|
def pop(self, *args):
|
|
result = self.__collection.pop(*args)
|
|
|
|
if self.entered:
|
|
result.__exit__(None, None, None)
|
|
|
|
return result
|
|
|
|
def remove(self, value):
|
|
if self.entered:
|
|
value.__exit__(None, None, None)
|
|
return self.__collection.remove(value)
|
|
|
|
def __isub__(self, other):
|
|
for i in other:
|
|
self.remove(i)
|
|
return self
|
|
|
|
def __sub__(self, other):
|
|
copied = self.copy()
|
|
copied -= other
|
|
return copied
|
|
|
|
def __len__(self):
|
|
return len(self.__collection)
|
|
|
|
def __delitem__(self, key):
|
|
return self.pop(key)
|
|
|
|
def __setitem__(self, key, value):
|
|
self.pop(key)
|
|
self.insert(key, value)
|
|
|
|
def __getitem__(self, key):
|
|
return self.__collection[key]
|
|
|
|
def count(self, *args, **kwargs):
|
|
return self.__collection.count(*args, **kwargs)
|
|
|
|
def index(self, *args, **kwargs):
|
|
return self.__collection.index(*args, **kwargs)
|
|
|
|
def __iter__(self):
|
|
return iter(self.__collection)
|
|
|
|
def __contains__(self, item):
|
|
return item in self.__collection
|
|
|
|
def __reversed__(self):
|
|
return reversed(self.__collection)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
class FooBar:
|
|
def __init__(self, a, b, c):
|
|
self.mhm = a
|
|
self.b = b
|
|
self.c = c
|
|
|
|
def ok(self):
|
|
return self.b
|
|
|
|
@property
|
|
def prop(self):
|
|
return self.c
|
|
|
|
# print attributes
|
|
# print(vars(FooBar()))
|
|
# print properties and methods
|
|
# print(vars(FooBar).keys())
|
|
blist = BatchList([FooBar('foo', 'bar', 'yay')])
|
|
blist += [FooBar('foobar', 'barfoo', 'yay foobar')]
|
|
print('mhm', blist.mhm)
|
|
print('prop', blist.prop)
|
|
# print('ok', blist.ok)
|
|
print('ok call', blist.ok())
|