119 lines
3.1 KiB
Python
119 lines
3.1 KiB
Python
from sqlalchemy.orm.attributes import InstrumentedAttribute
|
|
from .utils import str_coercible
|
|
|
|
|
|
@str_coercible
|
|
class Path(object):
|
|
def __init__(self, path, separator='.'):
|
|
if isinstance(path, Path):
|
|
self.path = path.path
|
|
else:
|
|
self.path = path
|
|
self.separator = separator
|
|
|
|
def __iter__(self):
|
|
for part in self.path.split(self.separator):
|
|
yield part
|
|
|
|
def __len__(self):
|
|
return len(self.path.split(self.separator))
|
|
|
|
def __repr__(self):
|
|
return "%s('%s')" % (self.__class__.__name__, self.path)
|
|
|
|
def __getitem__(self, slice):
|
|
result = self.path.split(self.separator)[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(object):
|
|
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):
|
|
for part in self.parts:
|
|
yield part
|
|
|
|
def __invert__(self):
|
|
def get_backref(part):
|
|
prop = part.property
|
|
backref = prop.backref
|
|
if backref is None:
|
|
raise Exception(
|
|
"Invert failed because property '%s' of class "
|
|
"%s has no backref." % (
|
|
prop.key,
|
|
prop.mapper.class_.__name__
|
|
)
|
|
)
|
|
return backref
|
|
|
|
return self.__class__(
|
|
self.parts[-1].mapper.class_,
|
|
'.'.join(map(get_backref, reversed(self.parts)))
|
|
)
|
|
|
|
def __getitem__(self, slice):
|
|
result = self.parts[slice]
|
|
if isinstance(result, list):
|
|
if result[0] is self.parts[0]:
|
|
class_ = self.class_
|
|
else:
|
|
class_ = result[0].mapper.class_
|
|
return self.__class__(
|
|
class_,
|
|
self.path[slice]
|
|
)
|
|
else:
|
|
return result
|
|
|
|
def __len__(self):
|
|
return len(self.path)
|
|
|
|
def __repr__(self):
|
|
return "%s(%r, %r)" % (
|
|
self.__class__.__name__,
|
|
self.class_,
|
|
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)
|