forked from Raiza.dev/EliteBot
152 lines
4 KiB
Python
152 lines
4 KiB
Python
import sqlalchemy as sa
|
|
from sqlalchemy.orm.attributes import InstrumentedAttribute
|
|
from sqlalchemy.util.langhelpers import symbol
|
|
|
|
from .utils import str_coercible
|
|
|
|
|
|
@str_coercible
|
|
class Path:
|
|
def __init__(self, path, separator='.'):
|
|
if isinstance(path, Path):
|
|
self.path = path.path
|
|
else:
|
|
self.path = path
|
|
self.separator = separator
|
|
|
|
@property
|
|
def parts(self):
|
|
return self.path.split(self.separator)
|
|
|
|
def __iter__(self):
|
|
yield from self.parts
|
|
|
|
def __len__(self):
|
|
return len(self.parts)
|
|
|
|
def __repr__(self):
|
|
return f"{self.__class__.__name__}('{self.path}')"
|
|
|
|
def index(self, element):
|
|
return self.parts.index(element)
|
|
|
|
def __getitem__(self, slice):
|
|
result = self.parts[slice]
|
|
if isinstance(result, list):
|
|
return self.__class__(
|
|
self.separator.join(result),
|
|
separator=self.separator
|
|
)
|
|
return result
|
|
|
|
def __eq__(self, other):
|
|
return self.path == other.path and self.separator == other.separator
|
|
|
|
def __ne__(self, other):
|
|
return not (self == other)
|
|
|
|
def __unicode__(self):
|
|
return self.path
|
|
|
|
|
|
def get_attr(mixed, attr):
|
|
if isinstance(mixed, InstrumentedAttribute):
|
|
return getattr(
|
|
mixed.property.mapper.class_,
|
|
attr
|
|
)
|
|
else:
|
|
return getattr(mixed, attr)
|
|
|
|
|
|
@str_coercible
|
|
class AttrPath:
|
|
def __init__(self, class_, path):
|
|
self.class_ = class_
|
|
self.path = Path(path)
|
|
self.parts = []
|
|
last_attr = class_
|
|
for value in self.path:
|
|
last_attr = get_attr(last_attr, value)
|
|
self.parts.append(last_attr)
|
|
|
|
def __iter__(self):
|
|
yield from self.parts
|
|
|
|
def __invert__(self):
|
|
def get_backref(part):
|
|
prop = part.property
|
|
backref = prop.backref or prop.back_populates
|
|
if backref is None:
|
|
raise Exception(
|
|
"Invert failed because property '%s' of class "
|
|
"%s has no backref." % (
|
|
prop.key,
|
|
prop.parent.class_.__name__
|
|
)
|
|
)
|
|
if isinstance(backref, tuple):
|
|
return backref[0]
|
|
else:
|
|
return backref
|
|
|
|
if isinstance(self.parts[-1].property, sa.orm.ColumnProperty):
|
|
class_ = self.parts[-1].class_
|
|
else:
|
|
class_ = self.parts[-1].mapper.class_
|
|
|
|
return self.__class__(
|
|
class_,
|
|
'.'.join(map(get_backref, reversed(self.parts)))
|
|
)
|
|
|
|
def index(self, element):
|
|
for index, el in enumerate(self.parts):
|
|
if el is element:
|
|
return index
|
|
|
|
@property
|
|
def direction(self):
|
|
symbols = [part.property.direction for part in self.parts]
|
|
if symbol('MANYTOMANY') in symbols:
|
|
return symbol('MANYTOMANY')
|
|
elif symbol('MANYTOONE') in symbols and symbol('ONETOMANY') in symbols:
|
|
return symbol('MANYTOMANY')
|
|
return symbols[0]
|
|
|
|
@property
|
|
def uselist(self):
|
|
return any(part.property.uselist for part in self.parts)
|
|
|
|
def __getitem__(self, slice):
|
|
result = self.parts[slice]
|
|
if isinstance(result, list) and result:
|
|
if result[0] is self.parts[0]:
|
|
class_ = self.class_
|
|
else:
|
|
class_ = result[0].parent.class_
|
|
return self.__class__(
|
|
class_,
|
|
self.path[slice]
|
|
)
|
|
else:
|
|
return result
|
|
|
|
def __len__(self):
|
|
return len(self.path)
|
|
|
|
def __repr__(self):
|
|
return "{}({}, {!r})".format(
|
|
self.__class__.__name__,
|
|
self.class_.__name__,
|
|
self.path.path
|
|
)
|
|
|
|
def __eq__(self, other):
|
|
return self.path == other.path and self.class_ == other.class_
|
|
|
|
def __ne__(self, other):
|
|
return not (self == other)
|
|
|
|
def __unicode__(self):
|
|
return str(self.path)
|