diff --git a/cubes/common.py b/cubes/common.py index 406526a2..a6046017 100644 --- a/cubes/common.py +++ b/cubes/common.py @@ -47,20 +47,31 @@ def __repr__(self): return "{%s}" % ", ".join(items) -def assert_instance(obj, class_, label): - """Raises ArgumentError when `obj` is not instance of `cls`""" - if not isinstance(obj, class_): +def assert_instance(obj, cls, label): + """Raises ArgumentError when `obj` is not instance of `cls`. + + * `cls` - can either be a type or a tuple of type (see also `isinstance`). + * `label` - the variable name on which the test is performed. + """ + if not isinstance(obj, cls): + if isinstance(cls, type): + class_name = cls.__name__ + else: + class_name = ' or '.join(c.__name__ for c in cls) raise ModelInconsistencyError("%s should be sublcass of %s, " "provided: %s" % (label, - class_.__name__, + class_name, type(obj).__name__)) -def assert_all_instances(list_, class_, label="object"): - """Raises ArgumentError when objects in `list_` are not instances of - `cls`""" +def assert_all_instances(list_, cls, label="object"): + """Raises ArgumentError when objects in `list_` are not instances of `cls`. + + * `cls` - can either be a type or a tuple of type (see also `isinstance`). + * `label` - the variable name on which the test is performed. + """ for obj in list_ or []: - assert_instance(obj, class_, label="object") + assert_instance(obj, cls, label=label) class MissingPackageError(Exception): diff --git a/cubes/metadata/base.py b/cubes/metadata/base.py index 5e31656c..ecd2df99 100644 --- a/cubes/metadata/base.py +++ b/cubes/metadata/base.py @@ -60,27 +60,30 @@ def to_dict(self, create_label=None, **options): return out - def localized(self, context): - """Returns a copy of the cube translated with `translation`""" - - acopy = self.__class__.__new__(self.__class__) - acopy.__dict__ = self.__dict__.copy() - - d = acopy.__dict__ + def clone(self, **attrs): + """Clone the model with modifications of his attributes. - for attr in self.localizable_attributes: - d[attr] = context.get(attr, getattr(self, attr)) - - for attr in self.localizable_lists: - list_copy = [] + * `attrs` - new values for each of his attributes. + """ + d = self.to_dict() + d.update(**attrs) + return type(self)(**d) - if hasattr(acopy, attr): - for obj in getattr(acopy, attr): - obj_context = context.object_localization(attr, obj.name) - list_copy.append(obj.localized(obj_context)) - setattr(acopy, attr, list_copy) + def localized(self, context): + """Returns a copy of the cube translated with `translation`""" - return acopy + changed_attr = { + attr: context.get(attr, getattr(self, attr)) + for attr in self.localizable_attributes + } + changed_attr.update({ + attr: [ + obj.localized(context.object_localization(attr, obj.name)) + for obj in getattr(self, attr) + ] + for attr in self.localizable_lists + }) + return self.clone(**changed_attr) def object_dict(objects, by_ref=False, error_message=None, error_dict=None): diff --git a/cubes/metadata/cube.py b/cubes/metadata/cube.py index 3517a660..74ce82bd 100644 --- a/cubes/metadata/cube.py +++ b/cubes/metadata/cube.py @@ -43,6 +43,10 @@ IMPLICIT_AGGREGATE_LABELS.update(aggregate_calculator_labels()) +def _replace_all_dict_by_instance(sequence, cls): + return [cls(**o) if isinstance(o, dict) else o for o in sequence] + + class Cube(ModelObject): """Logical representation of a cube. @@ -249,6 +253,7 @@ def __init__(self, name, dimensions=None, measures=None, aggregates=None, # Measures measures = measures or [] + measures = _replace_all_dict_by_instance(measures, Measure) assert_all_instances(measures, Measure, "measure") self._measures = object_dict(measures, error_message="Duplicate measure {key} " @@ -258,6 +263,7 @@ def __init__(self, name, dimensions=None, measures=None, aggregates=None, # Aggregates # aggregates = aggregates or [] + measures = _replace_all_dict_by_instance(measures, MeasureAggregate) assert_all_instances(aggregates, MeasureAggregate, "aggregate") self._aggregates = object_dict(aggregates, @@ -267,6 +273,7 @@ def __init__(self, name, dimensions=None, measures=None, aggregates=None, # We don't need to access details by name details = details or [] + measures = _replace_all_dict_by_instance(measures, Attribute) assert_all_instances(details, Attribute, "detail") self.details = details